%% $Id: xycurve.doc,v 2.12 1994/10/25 03:01:14 ross Exp $ %% XY-pic ``Curves and Splines'' extension. %% Copyright (c) 1993-1994 Ross Moore %% This file is part of the XY-pic package for graphs and diagrams in TeX. %% See the companion README and INSTALL files for further information. %% Copyright (c) 1991-1994 Kristoffer H. Rose %% The XY-pic package is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by the %% Free Software Foundation; either version 2 of the License, or (at your %% option) any later version. %% The XY-pic package is distributed in the hope that it will be useful, but %% WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY %% or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License %% for more details. %% You should have received a copy of the GNU General Public License along %% with this package; if not, write to the Free Software Foundation, Inc., %% 675 Mass Ave, Cambridge, MA 02139, USA. \ifx\xyloaded\undefined \input xy \fi \xyprovide{curve}{Curve and Spline extension}{\stripRCS$Revision: 2.12 $}% {Ross Moore}{ross@mpce.mq.edu.au}% {Mathematics Department, Macquarie University, NSW~2109, Australia} \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% This option provides \XY-pic with the ability to typeset spline curves and to construct curved connections using arbitrary directional objects. "Warning": Using curves can be quite a strain on \TeX's memory; you should therefore limit the length and number of curves used on a single page. Memory use is less when combined with a backend capable of producing its own curves; \eg, the \PS\ backend). \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Header:}\leavevmode \DOCHEADER \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \message{curve,} \DOCMODE) Simple ways to specify curves in \XY-pic are as follows: \begin{defs} |**\crv{||}| & curved connection \cr |**\crvs{||}| & get from the stack \cr |\curve{||}| & as a ation \cr \end{defs} in which is a list of valid itions. The decoration form |\curve| is just an abbreviation for |\connect\crv|. As usual, the current $p$ and $c$ are used as the start and finish of the connection, respectively. Within the itions are separated by |&|. A full description of the syntax for |\crv| is given in figure~??[f.curve]. \begin{code} \xy (0,20)*[o]+{A};(60,0)*[o]+{B}="B" **\crv{}\POS?(.4)*_+!UR{0},"B" **\crv{(30,30)}\POS?*^+!D{1},"B" **\crv{(20,40)&(40,40)} \POS?*^+!D{2},"B" **\crv{(10,20)&(30,20)&(50,-20)&(60,-10)} \POS?*+^!UR{4} \endxy \end{code} $$\docode$$\goodbreak\goodbreak \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% We need a counter to track the number of control points, (i.e. the number of s in ) and provide a macro to read it. Also a token list which will store the vital information for later use, to allow curved connections to work properly. \DOCMODE( \xynew@{count}\crv@cnt@ \xydef@\xynumctrlpts@{\the\crv@cnt@} \xynew@{toks}\crvpts@ \DOCMODE) \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% If is empty a straight connection is computed. When the length of is one or two then the curve is uniquely determined as a single-segment B\'ezier quadratic or cubic spline. The tangents at $p$ and $c$ are along the lines connecting with the adjacent control point. With three or more itions a cubic B-spline construction is used. B\'ezier cubic segments are calculated from the given control points. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\setcurve@{% \edef\xysplineparams@{% \expandafter\noexpand\csname params@\endcsname}% \edef\xysplineedges@{% \expandafter\noexpand\csname edges@\endcsname}% \edef\xycrvcnt@{% \expandafter\noexpand\csname crvcnt@\endcsname}% \expandafter\edef\xycrvcnt@{\number\crv@cnt@}% \ifcase\crv@cnt@ \expandafter\xycvxhull@\or \expandafter\xyquadbezier@\or \expandafter\xycubicbezier@\or \expandafter\xybspline@iii\or \expandafter\xybspline@iv\else \expandafter\xybspline@ \fi} \DOCMODE) \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The previous picture was typeset using: \begin{code} \xy (0,20)*+{A};(60,0)*+{B} **\crv{} **\crv{(30,30)} **\crv{(20,40)&(40,40)} **\crv{(10,20)&(30,20)&(50,-20)&(60,-10)} \endxy \end{code} \displaycode \noindent except for the labels, which denote the number of entries in the . (Extending this code to include the labels is set below as an exercise). \begin{code} \xy (0,20)*[o]+{A};(60,0)*[o]+{B}="B" **\crv{} \POS?(.4)*_+!UR{0},"B" **\crv{(30,30)} \POS?*^+!D{1},"B" **\crv{(20,40)&(40,40)} \POS?*^+!D{2},"B" **\crv{(10,20)&(30,20)&(50,-20)&(60,-10)} \POS?*+^!UR{4} \endxy \end{code} \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The |?|-operator of \S??g[xy.doc:pos] (note~??g[xy.doc:]) finds arbitrary s along a curve in the usual way. \begin{exercise} Extend the code given for the curves in the previous picture so as to add the labels giving the number of control points. \answertext{This is the code that was actually used:} \answercode \answertext\displaycode \end{exercise} Using |?| will set the current direction to be tangential at that , and one can specified distances along the curve from a found using the |?|\dots|/||/| notation: \begin{code} \xy (0,20)*+{A};(60,0)*+{B} **\crv{(10,20)&(30,20)&(50,-20)&(60,-10)} ?<*\dir{<} ?>*\dir{>} ?(.65)*{\oplus} *!LD!/^-5pt/{x} ?(.65)/12pt/*{\oplus} *!LD!/^-5pt/{x'} ?(.28)*=0{\otimes}-/40pt/*+{Q}="q" +/100pt/*+{P};"q" **\dir{-} \endxy \end{code} $$\docode$$ \begin{exercise} Suggest code to produce something like the above picture; the spline curve is the same as in the previous picture. "Hints": The line is 140pt long and touches $0.28$ of the way from $A$ to $B$ and the $x$ is 0.65 of the way from $A$ to $B$. \answertext{This is the code that was used to typeset the picture:} \answercode \answertext\displaycode \end{exercise} \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The positions in specify {\em control points\/} which determine the initial and final directions of the curve---leaving $p$ and arriving at $c$---and how the curve behaves in between, using standard spline constructions. In general, control points need not lie upon the actual curve. A natural spline parameter varies in the interval $[0,1]$ monotonically along the curve from $p$ to $c$. This is used to specify s along the curve, however there is no easy relation to arc-length. Generally the parameter varies more rapidly where the curvature is greatest. The following diagram illustrates this effect for a cubic spline of two segments (3 control points). \begin{code} \def\ssz#1{\hbox{$_{^{#1}}$}} \xy (0,0)*+{A};(30,-10)*+{B}="B",**\dir{-}, "B"**\crv{(5,20)&(20,25)&(35,20)} ?<(0)*\dir{<}="a" ?>(1)*\dir{>}="h" ?(.1)*\dir{<}="b" ?(.9)*\dir{>}="i" ?(.2)*\dir{<}="c" ?(.8)*\dir{>}="j" ?(.3)*\dir{<}="d" ?(.7)*\dir{>}="k" ?(.4)*\dir{<}="e" ?(.6)*\dir{>}="l" ?(.5)*\dir{|}="f", "a"*!RC\txt{\ssz{(\lt)}}; "h"*!LC\txt{\ssz{\;(\gt)}},**\dir{.}, "b"*!RD{\ssz{.1}}; "i"*!L{\ssz{\;.9}},**\dir{-}, "c"*!RD{\ssz{.2}}; "j"*!L{\ssz{\;.8}},**\dir{-}, "d"*!RD{\ssz{.3}}; "k"*!L{\ssz{\;.7}},**\dir{-}, "e"*!RD{\ssz{.4}}; "l"*!LD{\ssz{.6}},**\dir{-}, "f"*!D!/^-3pt/{\ssz{.5}} \endxy \end{code} $$\docode$$ \begin{exercise} Write code to produce a picture such as the one above. ("Hint": Save the locations of places along the curve for later use with straight connections.) \answertext{Here is the code that was used to typeset the picture:} \answercode \answertext\displaycode \end{exercise} \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% To have the same occuring as a multiple control point simply use a delimiter, which leaves the unchanged. Thus |\curve{||&}| uses a cubic spline, whereas |\curve{||}| is quadratic. Repeating the same control point three times in succession results in straight segments to that control point. Using the default styles this is an expensive way to get straight lines, but it allows for extra effects with other styles. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Curve Objects:} At present the syntax is supported for using a |\curve| object only as a decoration, constructed from a |\crv| object used as a connection. \DOCMODE( \xydef@\curve{\connect\crv} \DOCMODE) The more general |\crv| object currently works as a connection. In future this object-type will be extended to allow closed curves as the boundaries of objects. \DOCMODE( \xydef@\crv#1#{\hbox \bgroup \initshape@ \crv@{#1}} %\xydef@\crvo#1#{\hbox \bgroup \initshape@ \crv@{#1}} \xydef@\crv@#1#2{% \DN@{#1}\ifx\empty\next@ \DN@{\def\afterCURVEaction@{}\parsecurve@\xycurve@@}% \else\DN@{\parseCURVE@#1\parsecurve@\curveSTYLE@}\fi \next@ #2\@endcurve\endcrv@ } \xydef@\curveSTYLE@{\xycurve@@}% default style \xydef@\afterCURVEaction@{}% default after-curve action \xydef@\@endcurve{}% \xydef@\endcrv@@{\endcrv@\POS}% \DOCMODE) The |\@endcurve| inserted here ensures that subsequent parsing with |\checkendcurve@| (see below) will terminate cleanly. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The |\crvs| object has restricted functionality with choice of styles and is always used for curves only. It has a single braced argument which can be used to set curve styles. Its main use is for re-typesetting different portions of the same curve, for example the subsegments of a curved arrow/path segment. This is done by first examinining |\bstartPLACE@|. If |\relax| or undefined, then the curve is set using |\@crv@| to decide the style. If |\bstartPLACE@| is |\empty| then the curve is fully processed as a connection but nothing is actually typeset; this is used by curved arrows where the curve is first set as ``invisible'' before breaks and labels are processed. When |\bstartPLACE@| contains a number, normally within the range 0 to 1, this is interpreted as a along a curved connection that has already been established. No typesetting may occur before this . In this case the control point information is discarded since the curve can be recovered using |\splinereset@|. The current $p$ and $c$ are no longer the endpoints of the curve but are itions along the curve between which the typesetting should occur. The value of |\bstartPLACE@| is typically a along the curve which is within the at $p$. It is used to help locate the edge of this where typesetting should commence. Similarly |\bendPLACE@| is typically a along the curve which is within the at $c$. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xywarnifdefined\crv@normaltemplate \xywarnifdefined\crv@othertemplate {\xyuncatcodes \catcode`\@=11 \catcode`\#=6 \gdef\crv@normaltemplate#1{{}{~**#1\xy@@crvaddstack@}} \gdef\crv@othertemplate#1{{}{~*=<2\jot>{}~**#1\xy@@crvaddstack@}} \xydef@\crvs#1#{\hbox \bgroup \DN@{\initshape@ \@crv@{#1}}% \ifx\bstartPLACE@\relax \else \ifx\bstartPLACE@\empty\else \splinetrace@{bstartPLACE@=\bstartPLACE@, bendPLACE@=\bendPLACE@}% \DN@{\let\xy@@crvaddstack@=\xy@@samecurve@ \let\saveshape@=\savesame@ \let\savectrlptsnum@=\relax \let\startxycurve@=\recovercurve@ \crv@cnt@=\xycrvptsnum@\relax \@crv@{#1}}% \fi\fi \next@ } \xydef@\xy@@samecurve@{\xyFN@\checkendcurve@} \xydef@\savesame@{\egroup } \xydef@\recovercurve@{\crv@cnt@=\xycrvptsnum@ \splinereset@ \edef\cv@end{\cfromthec@}% \edef\cv@start{\cfromthep@}\splinereset@ } \xydef@\@crv@#1#2{\DN@{#1#2}% \ifx\next@\empty \DN@{\crv@{}{\xy@@crvaddstack@}}% \else\def\tmp@{-}\ifx\next@\tmp@ \DN@{\crv@{}{\xy@@crvaddstack@}}% \else\def\tmp@{=}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{=}}}% \else\def\tmp@{2-}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{2.}}}% \else\def\tmp@{3-}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{3.}}}% \else\def\tmp@{--}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{--}}}% \else\def\tmp@{==}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaledtemplate{\dir2{--}}}% \else\def\tmp@{2--}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaledtemplate{\dir2{--}}}% \else\def\tmp@{3--}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir3{--}}}% \else\def\tmp@{.}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{.}}}% \else\def\tmp@{..}\ifx\next@\tmp@ \DN@{\expandafter\crv@\crv@normaltemplate{\dir{.}}}% \else \DN@{\expandafter\crv@\crv@othertemplate{\dir#1{#2}}}% \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \next@} \DOCMODE) \BUG: this should use a |\Step@@| method to get the spacing for dotting; this will eliminate the need for templates\dots \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{figure*}[tp] \begin{syntax} \multispan{3}{{\tt\string\curve}|{||}|\hfil} & construct curved connection \noalign{\nobreak\smallskip\nobreak\hrule\nobreak\smallskip\nobreak} &\iss & & zero or more modifiers possible; default is |~C| &\orr & |~| & set \noalign{\smallskip} &\iss & |p|\orr|P|\orr|l|\orr|L|\orr|c|\orr|C| & ??!^[show only] control points (|p|=points), joined by lines (|l|=lines), or curve only (|c|=curve) &\orr & |pc|\orr|pC|\orr|Pc|\orr|PC| & show ??!^[control points] ??!^[and curve] &\orr & |lc|\orr|lC|\orr|Lc|\orr|LC| & show ??!^[lines joining] control points ??!^[and curve] &\orr & |cC| & plot curve twice, with and without specified formatting \noalign{\smallskip} &\iss & & use the appropriate default style & \orr & |~*| & specify the ??!^[``drop'' object] and ??!^[maybe more] & \orr & |~**| & specify the ??!^[``connect'' object] and ??!^[maybe more] \noalign{\smallskip} &\iss & \orr\ & list of positions for the control points &\orr & |~@| \orr\ |~@| & add the current ??!^[stack] to the control points \noalign{\smallskip} & \iss & {\tt\&} & allowable delimiter \end{syntax} \caption{Syntax for curves.} ??=[f.curve] \vfil \end{figure*} \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Parsing:} Two separate parsers are required: one for |\curve...|, the other for the contents of |{...}|. Set |\curveSTYLE@| to be the default of |\xycurve@@| then examine the following tokens to see if this must be changed. Set |\curveSTYLE@| to be the default of |\xycurve@@| then examine the following tokens to see if this must be changed. "Procedure": If the first token of |#1| (from |\crv@|) is |~| then a letter should follow, determining how to set the style. If instead it is an active control sequence then issue a warning message, but let it do its thing anyway. However |\parsecurve@| is a normal thing to encounter, so no warning is required. Currently a single letter without the |~| will be recognised, but two-letter combinations definitely need the |~|. \DOCMODE( \xydef@\parseCURVE@{\def\curveSTYLE@{\xycurve@@}\def\afterCURVEaction@{}% \xyFN@\parseCURVE@@} \xydef@\parseCURVE@@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\parseCURVE@@}% gobble spaces \else\ifx ~\next \DN@ ~{\xy@~{}\xyFN@\setcurveSTYLE@@}% \else\ifx\parsecurve@\next\DN@{}% \else\ifcat\active\noexpand\next\DN@{\xyactive@Warning}% \else\ifcat a\next\DN@##1{\xytilde@Warning\setcurveSTYLE@{##1}% \xyFN@\parseCURVE@@}% \else\DN@{}\fi\fi\fi\fi\fi \next@ }% \DOCMODE) Options are normally specified using |~|. Sometimes this can be safely omitted; if such a case is encountered then a warning message is issued advising to use |~|. This nicety may be removed in future versions. \DOCMODE( \xydef@\xyactive@Warning{\xywarning@{expandable control sequences should not be here, results are unpredictable in diagrams.}}% (2) \xydef@\xytilde@Warning{Please use ~ to set curve style.}% (3a) \DOCMODE) Examine the next token to see if it determines a curve style. Currently only |p|, |l| and |c| are recognised, along with the uppercase variants |P|, |L| and |C|. When one of these letters is encountered, set |\curveSTYLE@| and keep examining tokens with |\setafterCURVEaction@|. \DOCMODE( \xydef@\setcurveSTYLE@#1{\xyFN@\setcurveSTYLE@@#1} \xydef@\setcurveSTYLE@@{% \ifx\next p\def\curveSTYLE@{\xyc@trlpts@@}% \DN@ p{\xyFN@\setafterCURVEaction@}% \else\ifx\next P\def\curveSTYLE@{\xyc@trlpts@}% \DN@ P{\xyFN@\setafterCURVEaction@}% \else\ifx\next l\def\curveSTYLE@{\xyc@vxhull@@}% \DN@ l{\xyFN@\setafterCURVEaction@}% \else\ifx\next L\def\curveSTYLE@{\xyc@vxhull@}% \DN@ L{\xyFN@\setafterCURVEaction@}% \else\ifx\next c\def\curveSTYLE@{\xycurve@@}% \DN@ c{\xyFN@\setafterCURVEaction@}% \else\ifx\next C\def\curveSTYLE@{\xycurve@@}% \DN@ C{\xyFN@\setafterCURVEaction@}% \else \DN@##1{\xywarning@{unknown curve style ##1}\xyFN@\parseCURVE@@}% \fi\fi\fi\fi\fi\fi \next@ } \DOCMODE) |\setafterCURVEaction@| examines tokens to see if a 2-letter combination is being specified; e.g. |pc|, |lC|, etc. Alternatively another |~| can set a new specification. \DOCMODE( \xydef@\setafterCURVEaction@{% \ifx\next~\DN@~{\xyFN@\setcurveSTYLE@@}% \else\ifcat a\next\DN@{\setafterCURVEaction@@}% \else\DN@{\xyFN@\parseCURVE@@}% \fi\fi \next@} \DOCMODE) Currently the only 2-letter specifications have either |c| or |C| as the 2nd letter. \DOCMODE( \xydef@\setafterCURVEaction@@{% \ifx\next c\DN@ c{% \def\afterCURVEaction@{\noexpand\endcurve\noexpand\xy@curve@}% \xyFN@\parseCURVE@@ }% \else\ifx\next C\DN@ C{% \def\afterCURVEaction@{\noexpand\endcurve\noexpand\xy@curve@@}% \xyFN@\parseCURVE@@ }% \else \DN@##1{\xywarning@{unknown after-curve action ##1}% \xyFN@\parseCURVE@@ }% \fi\fi \next@ } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% When |\parsecurve@| is encountered, this signifies the end of this part of the parsing. The only active control sequence that should be encountered legitimately here is |\@endcurve|, which signifies that the is empty and default styles are required. Otherwise assume we the subsequent tokens are from |#2| of |\crv@|. Control passes to |\xycurve@@| to prepare for reading the s and . \DOCMODE( \xydef@\parsecurve@{\xyFN@\parsecurve@@} \xydef@\parsecurve@@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\parsecurve@@}% \else\ifx\next\@endcurve\DN@\@endcurve{\checkafterCURVE@}% \else\ifcat\active\noexpand\next\DN@{\relax}% \else\DN@{\xycurve@@}\fi\fi\fi\next@}% \DOCMODE) \DOCMODE( \xydef@\xycurve@{\begingroup\afterCURVE{\setcurve@\endgroup}\crvobjects@ \startxycurve@\xycrvmods@} \xydef@\xycurve@@{\afterCURVE{\setcurve@}\crvobjects@\startxycurve@ \xycrvmods@} \xydef@\xy@curve@{\xy@curve@@@{\splinetolerance\z@\crvobjects@}} \xydef@\xy@curve@@{\xy@curve@@@{\resetcrvobjects@}} \xydef@\xy@curve@@@#1{\ifx\cv@start\relax \DN@{\xywarning@{There is no curve to plot}}. \else\DN@{\begingroup\afterCURVE{\setcurve@\endgroup}% #1\cv@end}\fi \next@ } \xydef@\crvobjects@{\def\xycrvdrop@{}\def\xycrvconn@{}} \xydef@\resetcrvobjects@{\crvobjects@} \DOCMODE) These were originally provided for sophisticated-user access. They are otherwise undocumented and may be removed. \DOCMODE( \xylet@\savecurve=\xycurve@@ \xylet@\samecurve=\xy@curve@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |\xy@crvmods@| handles reading the s, \ie\ the ``drop'' object and the ``connect'' object. \medskip "Procedure":??_[crvob1]~Get next token.??_[crvob2]~Is it a |~|? ??_[crvob4]~If not, exit. ??_[crvob2a]~If so, is it followed by a |*|? ??_[crvob2b]~If not, issue a message and go back to ??_[crvob1]. Otherwise, ??_[crvob3]~is there a second |*|? ??_[crvob3a]~If not then read and store the ``drop'' object, ??_[crvob3b]~else, read and store the ``connect'' object. When finished, in either case, ??_[crvob5]~look for further specifications. ??_[crvob3c]~An empty ``drop'' object is not allowed, so this actually gives the default of |\zerodot|. \DOCMODE( \xydef@\xycrvmods@{\xyFN@\xy@crvmods@}%?*[crvob1] \xydef@\xy@crvmods@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\xy@crvmods@}%gobble spaces \else\ifx\next ~\DN@ ~{\xyFN@\xy@crvmods@@}%?*[crvob2] \else\DN@{\xy@crvmods@@@@}\fi\fi \next@}%?*[crvob4] \xydef@\xy@crvmods@@@@{\xyFN@\checkendcurve@}% \xydef@\xy@crvmods@@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\xy@crvmods@@}%gobble spaces \else\ifx\next *\DN@ *{\xyFN@\xy@@crvmods@}%?*[crvob2a] \else\addAT@\ifx\next\addAT@\DN@{\xyFN@\xy@@crvaddstack}% \else\DN@{\xywarning@{badly-formed curve option}\xyFN@\xy@crvmods@}%?*[crvob2b] \fi\fi\fi \next@}% \xydef@\xy@@crvmods@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\xy@@crvmods@}%gobble spaces \else\ifx\next *\DN@ *{\xyFN@\xy@@crvmods@@}%?*[crvob3] \else\DN@{\xy@crv@mods@@}\fi\fi \next@}%?*[crvob3a] \xydef@\xy@crv@mods@@#1#{\DN@##1{\xy@crvmods@@@{#1}{##1}}\next@}% \xydef@\xy@crvmods@@@#1#2{% \DN@{#1#2}\ifx\next@\empty \def\xycrvdrop@{ #1{\zerodot}}%?*[crvob3c] \else \def\xycrvdrop@{ #1{#2}}\fi \xyFN@\xy@crvmods@ }%?*[crvob5] \xydef@\xy@@crvmods@@#1#{\DN@##1{\xy@@crvmods@@@{#1}{##1}}\next@}%?*[crvob3b] \xydef@\xy@@crvmods@@@#1#2{\def\xycrvconn@{ #1{#2}}\xyFN@\xy@crvmods@ }% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Parsing of the is quite simple, recognising few special tokens: \begin{itemize} \item |\endcurve| and |\endxy| terminate reading of control points. \item |&|, |\\| and |\relax| delimit $$itions \item anything else is treated as a $$, being handled by the |\POS@| parser. \end{itemize} \DOCMODE( \xydef@\checkendcurve@{% \ifx\space@\next\expandafter\DN@\space{\xyFN@\checkendcurve@}%gobble spaces \else\ifx\next\endcurve\DN@\endcurve{\clearsearchflag@\endxycurve@}% \else\ifx\next\@endcurve\DN@\@endcurve{\checkafterCURVE@}% \else\ifx\next\xy@@crvaddstack@\DN@{}% \else\ifx\next\endxy\DN@{\clearsearchflag@\sloppyendcrv@}% \else\addAND@\ifx\next\addAND@\DN@{\clearsearchflag@\searchnextpt@}% \else\ifx ~\next\DN@ ~{\xyFN@\checkcrvtilde@}% \else\ifx\next\relax\DN@\relax{\clearsearchflag@\searchnextpt@}% \else\ifx\next\\\DN@\\{\clearsearchflag@\searchnextpt@}% \else\ifx\searchflag@\relax \DN@{\setsearchflag@\searchnextpt@}% \else\DN@##1{\flagwarn@{##1}}\fi \fi\fi\fi\fi\fi\fi\fi\fi\fi \next@ } \xydef@\searchnextpt@{\afterPOS{\xy@@\addtocrv@ \xyFN@\checkendcurve@}} \DOCMODE) There could be a problem in that an invalid token would cause an infinite loop, passing back and forth between |\checkendcurve@| and |\POS@|. This is avoided by setting a flag |\setsearchflag@| when the |\POS@| parser is called, not following a valid delimiter. Encountering a valid delimiter clears the flag. If |\checkendcurve@| is called with the flag set, a warning message is issued and the token is skipped; the flag remains set. Processing continues, but it is possible that the wrong number of positions will be read; presumably there is some kind of error that needs to be fixed anyway. \DOCMODE( \xydef@\checkafterCURVE@{% \edef\tmp@{\noexpand\xyFN@\noexpand\checkendcurve@ % \afterCURVEaction@\noexpand\endcurve}\tmp@ } \xylet@\searchflag@=\relax \xydef@\setsearchflag@{\let\searchflag@=@} \xydef@\clearsearchflag@{\let\searchflag@=\relax} \xydef@\flagwarn@#1{% \ifx\next\@endcurve\DN@{\xyFN@\checkendcurve@\endcurve}% \else\setsearchflag@\DN@{\xyFN@\checkendcurve@}% \xywarning@{skipping invalid token \string#1\space in curve}% \fi \next@ } \DOCMODE) It is possible for the final |\endcurve| to be omitted, but only when the next token is |\endxy|. A warning message is written to the log file, protesting against this sloppy \TeX-ing.\smiley \DOCMODE( \xydef@\sloppyendcrv@{% \xywarning@{Please use \string\endcurve\space to end the curve.}\endxycurve@} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Creation of the |\crv| is completed when |\endcrv@| is encountered. At this point the usual methods |\Drop@@| and |\Connect@@| are defined. Extra information is retained, using |\saveshape@| and |\savecrvparams@| for the benefit of methods which treat the curve as a connection. \DOCMODE( \xydef@\endcrv@{\saveshape@ %\def\Upness@{0}% \Edgec={\splineEdge}\Invisible@false\Hidden@false \edef\Drop@@{\noexpand\ifInvisible@ \setbox\z@=\copy\voidb@x \noexpand\else \noexpand\wdz@=\z@ \noexpand\ht\z@=\z@ \noexpand\dp\z@=\z@ \boxz@ \noexpand\fi }% \edef\Connect@@{% \noexpand\setbox\z@=\noexpand\box\lastobjectbox@ \noexpand\wdz@=\z@ \noexpand\ht\z@=\z@ \noexpand\dp\z@=\z@ \noexpand\Drop@@ \noexpand\ifHidden@\noexpand\else \noexpand\ifx\noexpand\COORD@\noexpand\xymatrixCOORD@ \noexpand\else \noexpand\Xmin=\the\Xmin \noexpand\Xmax=\the\Xmax \noexpand\Ymin=\the\Ymin \noexpand\Ymax=\the\Ymax \noexpand\fi \noexpand\fi \savecrvparams@ \noexpand\crvconnect@ }% \wdz@=\z@ \ht\z@=\z@ \dp\z@=\z@ } \DOCMODE) The values of |\Xmin|, |\Xmax|, \etc\ are used to compute the extents |\Lc|, |\Rc|, \etc\ of the object. This information must be maintained after the group is closed, unless the ``hidden'' attribute is required or we are inside a matrix construction. In calculating the size of the box containing the curve |\Xmin|, |\Xmax|, \etc\ are initialised to describe the minimum rectangle enclosing $p$ and $c$. At the same time we save the current scope. \DOCMODE( \xydef@\initshape@{\savescope@ \ifdim\Xc>\Xp \Xmin=\Xp \Xmax=\Xc \else \Xmax=\Xp \Xmin=\Xc \fi \ifdim\Yc>\Yp \Ymin=\Yp \Ymax=\Yc \else \Ymax=\Yp \Ymin=\Yc \fi } \DOCMODE) |\saveshape| is used to pass necessary information up one level of grouping. \DOCMODE( \xydef@\saveshape@{% \savectrlptsnum@ \Rc=\Xmax \advance\Rc-\Xc \Lc=\Xc \advance\Lc-\Xmin \Uc=\Ymax \advance\Uc-\Yc \Dc=\Yc \advance\Dc-\Ymin \edef\tmp@{\egroup \Uc=\the\Uc \Dc=\the\Dc \Lc=\the\Lc \Rc=\the\Rc \noexpand\ifdim\noexpand\Xmin>\the\Xmin\noexpand\Xmin=\the\Xmin\noexpand\fi \noexpand\ifdim\noexpand\Ymin>\the\Ymin\noexpand\Ymin=\the\Ymin\noexpand\fi \noexpand\ifdim\noexpand\Xmax<\the\Xmax\noexpand\Xmax=\the\Xmax\noexpand\fi \noexpand\ifdim\noexpand\Ymax<\the\Ymax\noexpand\Ymax=\the\Ymax\noexpand\fi \savecrvparams@ }\tmp@ \computeLeftUpness@ } \xydef@\savecrvparams@{\crvpts@={\the\crvpts@}% \noexpand\def\noexpand\crvconnect@{\expandafter\noexpand\crvconnect@}}% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\splineEdge{\rectangleEdge} \DOCMODE) \TODO: This can be improved. For example, trace along the spline until an appropriate point is found. There may be more than one such point, so extra criteria may be required. \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{notes} \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[``drop'' object] The ``drop'' object is set once, then ``dropped'' many times at appropriately spaced places along the curve. If directional, the direction from $p$ to $c$ is used. Default behaviour is to have tiny dots spaced sufficiently closely as to give the appearance of a smooth curve. Specifying a larger size for the ``drop'' object is a way of getting a dotted curve (see the example in the next note). \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[``connect'' object] The ``connect'' object is also dropped at each place along the curve. However, if non-empty, this object uses the tangent direction at each place. This allows a directional object to be specified, whose orientation will always match the tangent. To adjust the spacing of such objects, use an empty ``drop'' object of non-zero size as shown here: \begin{code} \xy (0,0)*+{A}; (50,-10)*+{B} **\crv{~*=<4pt>{.} (10,10)&(20,0)&(40,15)} **\crv{~*=<8pt>{}~**!/-5pt/\dir{>}(10,-20) &(40,-15)} \endxy \end{code} $$\docode$$ \displaycode \noindent When there is no ``connect'' object then the tangent calculations are not carried out, resulting in a saving of time and memory; this is the default behaviour. \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[maybe more] The ``drop'' and ``connect'' objects can be specified as many times as desired. Only the last specification of each type will actually have any effect. (This makes it easy to experiment with different styles.) \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[show only] Complicated diagrams having several spline curves can take quite a long time to process and may use a lot of \TeX's memory. A convenient device, especially while developing a picture, is to show only the location of the control points or to join the control points with lines, as a stylized approximation to the spline curve. The s |~p| and |~l| are provided for this purpose. Uppercase versions |~P| and |~L| do the same thing but use any s that may be specified, whereas the lowercase versions use plain defaults: small cross for |~p|, straight line for |~l|. Similarly |~C| and |~c| set the spline curve using any specified s or as a (default) plain curve. \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[and curve] Use of |~p|, |~l|, etc. is extended to enable both the curve and the control points to be easily shown in the same picture. Mixing upper- and lower-case specifies whether the s are to be applied to the spline curve or the (lines joining) control points. See the examples accompanying the next two notes. \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[control points] By default the control points are marked with a small cross, specified by |*\dir{x}|. The ``connect'' object is ignored completely. \begin{code} \xy (0,0)*+{A};(50,-10)*+{B} **\crv~pC{~*=<\jot>{.}(10,-10)&(20,15) &(40,15)} \endxy \end{code} $$\docode$$ was typeset by \dots \displaycode \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[lines joining] With lines joining control points the default ``drop'' object is empty, while the ``connect'' object is |\dir{-}| for simple straight lines. If non-empty the ``drop'' object is placed at each control point. The ``connect'' object may be used to specify a fancy line style. \begin{code} \xy (0,0)*+{A};(50,-10)*+{B} **\crv~Lc{~**\dir{--}~*{\oplus}(20,20) &(35,15)} \endxy \end{code} $$\docode$$ was typeset by \dots \displaycode \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \note??=[stack] When a stack of itions has been established using the |@i| and |@+| commands, these positions can be used and are appended to the . \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\xy@@crvaddstack{% \ifx\next ~\xywarning@{there should be a poslist here, \string~@ ignored}% \DN@{\xy@crvmods@}% \else\DN@{\xy@@crvaddstack@}\fi \next@} \xydef@\xy@@crvaddstack@{\smapxy@@\addtocrv@ \xyFN@\checkendcurve@} \xydef@\checkcrvtilde@{% \addAT@\ifx\next\addAT@\DN@{\xy@@crvaddstack@}% \else\xywarning@{invalid token after ~, ignored}% \DN@##1{\xyFN@\checkendcurve@}% \fi \next@ } \DOCMODE) \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \end{notes} \noindent\NOTE: Curves will be accessible to users through a |\crv| command that makes a curve out of every directional. This is not finished yet. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Spline Methods} Each |\curve| has to set the sliding abilities. This is done by a call to |\crvconnect@@| which gives values to the methods |\Creset@@|, |\Cshavep@|, etc. These value depend on the style of the curve itself. \DOCMODE( \xydef@\crvconnect@@{% \def\Creset@@{\crvreset@}% \def\Cshavep@@{\crvshavep@}% \def\Cshavec@@{\crvshavec@}% \def\Cslidep@@{\crvslidep@}% \def\Cslidec@@{\crvslidec@}% \def\Calong@@{\crvalong@}% \DOCMODE) When straight lines are required \dots \DOCMODE( \xydef@\straitconnect@{\crvconnect@@ \let\Creset@@=\straitreset@ \let\Cshavep@@=\straitshavep@ \let\Cshavec@@=\straitshavec@ \let\Cslidep@@=\straitslidep@ \let\Cslidec@@=\straitslidec@ \let\Calong@@=\straitalong@ \DOCMODE) These are the actual methods used for a sequence of straight segments. \DOCMODE( \xydef@\straitreset@{\the\crvpts@ \edef\xycrvptsnum@{\csname ptsnum@\endcsname}% \def\segmentnum@{0}} \xydef@\straitslidep@{\setupDirection@ii \noCslidep@@ } \xydef@\straitslidec@{\setupDirection@ii \noCslidec@@ } \xydef@\straitshavep@{% \expandafter\ifnum\xycrvptsnum@>\z@ \def\segmentnum@{0}% \csname cv@1\endcsname \resetDirection@ \setupDirection@ii \fi \noCshavep@@ } \xydef@\straitshavec@{% \expandafter\ifnum\xycrvptsnum@>\z@ \expandafter\count@@\xycrvptsnum@\relax \bgroup \csname cv@\number\count@@\endcsname \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ \edef\segmentnum@{\xycrvptsnum@}% \resetDirection@ \setupDirection@ii \fi \noCshavec@@ } \xydef@\straitalong@#1{% \ifdim#1\p@<\z@ \xywarning@{parameter value #1 is negative, using 0}% \cfromp@ \def\PLACEf@{{0}}% \else \dimen@=#1\p@ \count@=\dimen@ \divide\count@\p@ \expandafter\count@@\xycrvptsnum@\relax \advance\count@@\@ne \advance\dimen@\segmentnum@\p@ \advance\count@\segmentnum@\relax \def\segmentnum@{0}% \ifnum\count@@<\count@ \xywarning@{parameter value #1 too large, using \the\count@@}% \count@=\count@@ \advance\count@\m@ne \edef\PLACEf@{{1}}% \else \advance\dimen@-\count@\p@ \ifdim\dimen@=\z@\ifnum\count@>\z@ \advance\count@\m@ne \advance\dimen@\p@ \fi\fi \edef\PLACEf@{{\expandafter\removePT@\the\dimen@}}% \fi \ifnum\count@=\z@\else \bgroup \csname cv@\number\count@\endcsname \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ \fi \advance\count@\@ne \ifnum\count@<\count@@ \splinetrace@{find place \the\dimen@\space along straight segment \the\count@}% \csname cv@\number\count@\endcsname \fi \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \fi \expandafter\noCalong@@\PLACEf@ \czeroEdge@ } \DOCMODE) \medskip When curved segments are required \dots \DOCMODE( \xydef@\splineconnect@{% \let\crvreset@=\splinereset@ \let\crvshavep@=\splineshavep@ \let\crvshavec@=\splineshavec@ \let\crvslidep@=\splineslidep@ \let\crvslidec@=\splineslidec@ \let\crvalong@=\splinealong@ \DOCMODE) These are the actual methods. {\em Reset} the spline parameters: \begin{description} \item[|\xycrvptsnum@|:] expands to a control sequence, whose name incorporates the correct scoping level, giving the number of control points. \item[|\the\crvpts@|:] reads the control point locations and other information from a token list. \item[|\xysplineparams@|:] set to a control sequence name, incorporating the correct scoping level, which will allow access to the Be\'zier control points for each spline segment, when required. \item[|\xysplineedges@|:] set to a control sequence name, incorporating the correct scoping level, which gives the spline parameters of the edges of objects at $p$ and $c$; \item[|\segmentnum@|:] reset to $0$. \item[|\splinecorrect@p| and |\splinecorrect@c|:] reset to $0$ until changed by a |\Cshavep@| or |\Cshavec@|. \item[|\splinelength@|:] reset to $0$ until changed by a |\Cshavep@|, |\Cshavec@| or |\Calong@|; this is needed by |\Cshavep@| and |\Cshavec@| to quickly locate where to slide from, especially when it is necessary to slide across more than a single spline segment. \item[|\splineplace@|:] reset to $.5$. \end{description} \DOCMODE( \xydef@\splinereset@{\the\crvpts@ \edef\xycrvptsnum@{\csname ptsnum@\endcsname}% \edef\xysplineparams@{% \expandafter\noexpand\csname params@\endcsname}% \edef\xysplineedges@{% \expandafter\noexpand\csname edges@\endcsname}% \def\segmentnum@{0}\def\splinecorrect@p{0}\def\splinecorrect@c{0}% \splinelength@=\z@ \def\splineplace@f{.5}% \DOCMODE) {\em Shaving} to the appropriate edge. This is simply a matter of reading the stored edge information and storing the appropriate value in |\splinecorrect@p| or |\splinecorrect@c|. The difficult part is to construct the correct control sequence name and then to extract the correct part of the text in its expansion. \DOCMODE( \xydef@\splineshavep@{\begingroup \edef\xysplineedges@{% \expandafter\noexpand\csname edges@\endcsname}% \expandafter\expandafter\expandafter\getsplineedges@\xysplineedges@ \edef\tmp@{\noexpand\removePT@\the\dimen5}% \edef\tmp@{\endgroup \noexpand\def\noexpand\splinecorrect@p{\tmp@}}\tmp@ \setupDirection@ii } \xydef@\splineshavec@{\begingroup \edef\xysplineedges@{% \expandafter\noexpand\csname edges@\endcsname}% \expandafter\expandafter\expandafter\getsplineedges@\xysplineedges@ \dimen@=-\dimen7\relax \expandafter\count@\xycrvptsnum@\relax \ifnum\count@>\@ne \advance\count@\m@ne \fi \advance\dimen@\count@\p@ \edef\tmp@{\noexpand\removePT@\the\dimen@}% \edef\tmp@{\endgroup \noexpand\def\noexpand\splinecorrect@c{\tmp@}}\tmp@ \setupDirection@ii } \xydef@\splineslidep@#1{\enter@{\pfromthep@}% \begingroup \splinealong@@{0}{#1}+\splineslidep@@@ \setupDirection@ii \leave@ }% \xydef@\splineslidep@@@{% \dX=\dimen5 \dY=\dimen7 \edef\tmp@{\postfind@}\tmp@ \dimen@=\splineval@ \expandafter\count@\xycrvptsnum@\relax \ifnum\count@>\tw@ \advance\count@\m@ne \advance\dimen@\segmentnum@\p@ \advance\dimen@-\p@ \fi \edef\tmp@{\endgroup \endgroup \noexpand\def\noexpand\xysplineval@{\the\splineval@}% \noexpand\def\noexpand\splinecorrect@p{\expandafter\removePT@\the\dimen@}% \splinetrace@{slide = \the\splinelength@}% \Xc=\dimen@i \Yc=\dimen3 % something may be wrong here!! \dX=\the\dX \dY=\the\dY % \noexpand\def\noexpand\segmentnum@{\segmentnum@}}\tmp@ } \xydef@\splineslidec@#1{\enter@{\pfromthep@}% \begingroup \expandafter\splinealong@@\PLACEf@{#1}+\splineslidec@@@ \setupDirection@ii \leave@ } \xydef@\splineslidec@@@{% \dX=\dimen5 \dY=\dimen7 \edef\tmp@{\postfind@}\tmp@ \dimen@=\splineval@ \expandafter\count@\xycrvptsnum@\relax \ifnum\count@>\tw@ \advance\count@\m@ne \advance\dimen@\segmentnum@\p@ \advance\dimen@-\p@ \dimen@=-\dimen@ \advance\dimen@\count@\p@ \else \dimen@=-\dimen@ \advance\dimen@\p@ \fi \edef\tmp@{\endgroup \endgroup \noexpand\def\noexpand\xysplineval@{\the\splineval@}% \noexpand\def\noexpand\splinecorrect@c{\expandafter\removePT@\the\dimen@}% \splinetrace@{slide = \the\splinelength@}% \Xc=\dimen@i \Yc=\dimen3 % \dX=\the\dX \dY=\the\dY % \noexpand\def\noexpand\segmentnum@{\segmentnum@}}\tmp@ } \xydef@\checkslidemore@#1{% \dimen@ii=-#1\relax\ifdim\dimen@ii<\z@\multiply\dimen@ii\m@ne\fi \dimen@=-\splinelength@ \advance\dimen@\dimen@ii\relax \ifdim\dimen@<\z@\dimen@ii=-\dimen@\else\dimen@ii=\dimen@\fi \ifdim\dimen@ii<.2\p@\DN@{\splineslidec@@ \leave@}%%%% ? \else \splinetrace@{not found yet, \the\dimen@ii\space still to go}% \dimen@ii=#1\relax\ifdim\dimen@ii<\z@\multiply\dimen@\m@ne\fi \DN@{\expandafter\splineslidemore@\expandafter{\the\dimen@}}\fi \next@}% \xydef@\splineslidemore@#1{% \expandafter\count@\segmentnum@\relax \ifdim#1<\z@\advance\count@\m@ne\dimen@=\p@ \else\advance\count@\@ne\dimen@=\z@\fi \ifnum\count@<\@ne \expandafter\splinesegment@\expandafter{\segmentnum@}% \DN@{\xywarning@{cannot slide beyond start of curve}% \spline@find{\z@}{\z@}\splineslidec@@ \leave@ }%%%% ? \else \expandafter\count@@\xycrvptsnum@\relax\advance\count@@\m@ne \expandafter\ifnum\count@@<\count@\relax \expandafter\splinesegment@\expandafter{\segmentnum@}% \DN@{\xywarning@{cannot slide beyond end of curve}% \spline@find{\p@}{\z@}\splineslidec@@ \leave@ }%%%%% ? \else \edef\segmentnum@{\the\count@}% \expandafter\splinesegment@\expandafter{\segmentnum@}% \splinetrace@{sliding onto segment \segmentnum@}% \expandafter\spline@find\expandafter{\the\dimen@}{#1}% \DN@{\checkslidemore@{#1}}% \fi\fi \next@ } \xydef@\splineslidec@@{% \dimen@=\splineval@ \advance\dimen@\segmentnum@\p@\advance\dimen@-\p@ \edef\tmp@{\endgroup \noexpand\def\noexpand\xysplineval@{\the\dimen@}% \splinetrace@{slide = \the\splinelength@}% \Xc=\the\dimen@i \Yc=\the\dimen3 \dX=\the\dimen5 \dY=\the\dimen7 \postfind@ \noexpand\def\noexpand\segmentnum@{\segmentnum@}}\tmp@ \setupDirection@ii} \DOCMODE) The method |\splinealong@#1| finds places along the curve, when |#1| is given as a factor, normally between 0 and 1. The location of the edges of the objects at $p$ and $c$ are stored in |\splinecorrect@p| and |\splinecorrect@c|. These are the places found if the factor is either 0 or 1. If an edge is non-zero then it is actually possible to move to points inside that edge by specifying a factor less than 0 or greater than 1. \DOCMODE( \xydef@\splinealong@#1{\def\postfind@{}\splinealong@@{#1}\z@+% \splineslidec@@}% \DOCMODE) The parameter |#2| in |\splinealong@@#1#2#3| allows for a subsequent slide through a given along the curve, from the place specifed by the factor in |#1|. The information supplied in |#1| must be converted to the segment number and parameter value of the specified point. These will be calculated and stored temporarily in |\count@| and |dimen@| before the values are passed respectively to |\splinesegment@| which chooses the segment, and |\spline@find| to locate the point on this segment. To do the calculation, first we correct for the parameter values of the edges of the objects at the end-points, which are stored in |\splinecorrect@p| and |\splinecorrect@c|. If the resulting number $x$ is not an integer then |\count@| is set to $1+\floor{x}$ while |\dimen@| becomes $(x-\floor{x})|\p@|$. If $x$ is an integer: $x=0$ gives |\count@=1| and |\dimen@=\z@|, otherwise $|\count@|=x$ and |\dimen@=\p@|. \DOCMODE( \xydef@\splinealong@@#1#2#3{% \edef\tmp@{#1}\relax \dimen@ii\tmp@\p@ \expandafter\count@\xycrvptsnum@\relax \ifnum\count@>\@ne \advance\count@\m@ne \fi \dimen@=-\count@\p@ \expandafter\advance\expandafter\dimen@\splinecorrect@p\dimen@ii \expandafter\advance\expandafter\dimen@\splinecorrect@c\dimen@ii \edef\tmp@{#1}\relax \dimen@=-\tmp@\dimen@ \relax \expandafter\advance\expandafter\dimen@\splinecorrect@p\p@ \splinetrace@{actual:\the\dimen@; f:\the\dimen@ii; p:\splinecorrect@p, c:\splinecorrect@c}% \count@=\dimen@ \divide\count@\p@ \DN@{\relax}% \def\tmp@{\relax}\ifx\next@\tmp@ \expandafter\ifnum\xycrvptsnum@<\count@ \xywarning@{parameter value #1 too large}\DN@{\relax}% \else \edef\xysplineval@{\the\dimen@}% \advance\dimen@-\count@\p@ \ifdim\dimen@=\z@ \splinetrace@{find knot point \the\count@}% \ifnum\count@=\z@ \count@=\@ne \else \dimen@=\p@ \fi \else \advance\count@\@ne \splinetrace@{find point \the\dimen@\space on spline segment \the\count@}% \fi \edef\segmentnum@{\the\count@}% \edef\next@{\begingroup\noexpand\splinesegment@{\the\count@}}% \edef\nextii@{% \noexpand\spline@find{\the\dimen@}{#2}\noexpand\checksplinefind@{#2}}% \ifx-#3\relax \ifdim\dimen@>\z@\relax \ifdim\dimen@<\p@ \dimen@=-\dimen@ \advance\dimen@\p@ \edef\nextii@{\noexpand\splinereverse@ %\noexpand\readsplineparams@ \noexpand\spline@find{\the\dimen@}{#2}\noexpand\checksplinefind@{#2}}% \fi\fi\fi % \else % \edef\nextii@{% % \noexpand\spline@find{\the\dimen@}{#2}\noexpand\checksplinefind@{#2}}% % \fi \fi\fi \expandafter\next@\nextii@ } \DOCMODE) This macro checks whether the required sliding has been carried out, or whether it is necessary to continue sliding along the next segment. \DOCMODE( \xydef@\checksplinefind@#1{% \dimen@ii=#1\relax \dimen@=\splinelength@ \ifdim\dimen@ii=\z@\DN@{}% \else \ifdim\dimen@ii<\z@\advance\dimen@\dimen@ii \else\advance\dimen@-\dimen@ii\fi \ifdim\dimen@<\z@\multiply\dimen@\m@ne\fi \ifdim\dimen@<.2\p@\DN@{}% \else \count@=\segmentnum@\relax \ifdim\dimen@ii<\z@\advance\dimen@ii\splinelength@ \ifnum\count@>\@ne\advance\count@\m@ne \edef\next@{\noexpand\splinesegment@{\the\count@}% \noexpand\spline@find\p@{\the\dimen@ii}% \noexpand\checksplinefind@{\the\dimen@ii}}% \else \xywarning@{cannot slide \the\dimen@ii\space before start}% \DN@{\splinesegment@\@ne \spline@find\z@\z@}\fi \else\advance\dimen@ii-\splinelength@ \count@@=\numcontrolpts \ifnum\count@<\count@@ \advance\count@\@ne \edef\next@{\noexpand\splinesegment@{\the\count@}% \noexpand\spline@find\z@{\the\dimen@ii}% \noexpand\checksplinefind@{\the\dimen@ii}}% \else \xywarning@{cannot slide \the\dimen@ii\space beyond end}% \DN@{\splinesegment@{\the\count@@}\spline@find\p@\z@}\fi \fi \fi\fi \next@ } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \TODO: Define an |\extendcurve| as a which allows the curve to be continued smoothly past the endpoint $c$. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The command |\clearcurve| was originally provided to discard the curve information, and return to the graphics state before the curve was read, if this is ever found to be necessary. This will probably be removed. \DOCMODE( \xydef@\xyclearcrv@{\cv@end\cv@restore\loop@ \expandafter\let\csname cv@\number\crv@cnt@\endcsname\relax \ifnum\crv@cnt@>0\advance\crv@cnt@\m@ne\repeat@ \let\cv@start=\relax \let\cv@end=\relax \let\cv@restore=\relax \POS} \xylet@\clearcurve=\xyclearcrv@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% A macro can be used within the modifier, to provide different labels at each control point. For example\dots \begin{code} \newcount\cptlabel\global\cptlabel=0 \def\nextcptlabel{\global\advance\cptlabel 1 % \number\cptlabel} \xy (0,0)*+{A}; (50,-10)*+{B} **\crv~Pc{~*{\nextcptlabel}(10,-10) &(20,15)&(30,-15)&(40,15)} \endxy \end{code} \displaycode $$\docode$$ \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Two control sequences are provided to access the control points. Firstly |\numcontrolpts| returns the number of them, while |\xycontrolpt| sets $c$ to be the particular control point. Here must be a single digit, grouped integer \eg\ |{-15}| or count register containing a numerical value. If larger than |\numcontrolpts| then $c$ becomes the end-point of the curve, while if zero it becomes the starting point. A negative value sets both $p$ and $c$ to be the starting point, whereas other values leave $p$ unchanged. \DOCMODE( \xylet@\numcontrolpts=\xynumctrlpts@ \xydef@\xycontrolpt@#1{{% \ifnum#1<\z@\aftergroup\cv@restore\aftergroup\cv@start \else \expandafter\count@\xycrvptsnum@\relax \advance\count@\@ne \ifnum#1>\count@ \ifx\cv@end\relax \expandafter\aftergroup\csname cv@\number#1\endcsname \else\aftergroup\cv@end\fi \else \expandafter\ifx\csname cv@\number#1\endcsname\relax \ifnum#1=\z@\aftergroup\cv@start\else\aftergroup\cv@end\fi \else\expandafter\aftergroup\csname cv@\number#1\endcsname \fi\fi\fi}}% \xylet@\xycontrolpt=\xycontrolpt@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Storing control point locations:} Upon starting to read the control points for a curve, first set the counter |\crv@cnt@| to be zero. Store the position of $p$ and $c$ and the current base in control sequences |\cv@start|, |\cv@end| and |\cv@restore| for later retrieval and to reinstate the current graphics state. |\cv@start| is a little more complicated in order to retain the existing value of $p$. \DOCMODE( \xydef@\startxycurve@{\crv@cnt@=\z@ \edef\cv@start{\cfromthep@}% \ifdim\Rc=\z@\relax\ifdim\Lc=\z@\relax\ifdim\Dc=\z@\relax \ifdim\Uc=\z@\relax \Edgec={\zeroEdge}% \fi\fi\fi\fi \edef\cv@end{\cfromthec@}% \edef\cv@restore{\pfromthep@\basefromthebase@}} \xylet@\cv@start=\relax \xylet@\cv@end=\relax \xylet@\cv@restore=\relax \xydef@\readxycurve@{\startxycurve@\xycrvmods@} \xylet@\readxycurve=\readxycurve@ \DOCMODE) The position of each control point is stored as the expansion of a control sequence whose name encodes its place within the $$. Expanding this control sequence sets $c$ to be the appropriate position. \DOCMODE( \xydef@\addtocrv@{\advance\crv@cnt@\@ne \edef\tmp@{% \expandafter\noexpand\csname cv@\number\crv@cnt@\endcsname}% \expandafter\edef\tmp@{\cfromthec@}% \edef\tmp@{\noexpand\addtocrvpts@{\noexpand\def \expandafter\noexpand\tmp@{\Xc=\the\Xc \Yc=\the\Yc }}}\tmp@ \edef\xycrvptsnum@{\the\crv@cnt@}% \DOCMODE) % use of |\xycrvptsnum@| here is not desirable, but it works. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Alternate curve styles:} \DOCMODE( \xydef@\xycrvdrop@{ {\zerodot}} \xydef@\xycrvconn@{} \xydef@\xyc@trlpts@{\def\xycrvdrop@{ \dir{x}}\def\xycrvconn@{}% \afterCURVE{\savecrvobjects@\xyctrlpts@}\readxycurve@} \xydef@\xyc@trlpts@@{% \afterCURVE{\savecrvobjects@\def\xycrvdrop@{ \dir{x}}% \def\xycrvconn@{}\xyctrlpts@}\readxycurve@} \xydef@\xyc@vxhull@{\def\xycrvdrop@{ {}}\def\xycrvconn@{ \dir{-}}% \afterCURVE{\savecrvobjects@\xycvxhull@}\readxycurve@} \xydef@\xyc@vxhull@@{% \afterCURVE{\savecrvobjects@\def\xycrvdrop@{ {}}% \def\xycrvconn@{ \dir{-}}\xycvxhull@}\readxycurve@} \xylet@\controlpts=\xyc@trlpts@ \xylet@\convexhull=\xyc@vxhull@ \xydef@\savecrvobjects@{\bgroup \expandafter\toks@\expandafter{\xycrvdrop@}% \expandafter\toks@ii\expandafter{\xycrvconn@}% \edef\tmp@{\egroup\noexpand\def\noexpand\resetcrvobjects@{% \noexpand\def\noexpand\xycrvdrop@{\the\toks@}% \noexpand\def\noexpand\xycrvconn@{\the\toks@ii}% }}\tmp@} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% There is the potential to do many other things here, by using the control sequence |\afterCURVE|, once the list of control points has been read. \DOCMODE( \xydef@\afterCURVE#1{\def\afterCURVE@{#1}} \xydef@\endxycurve@{\cv@end\savectrlptsnum@ \expandafter\def\csname params@\endcsname{\the\crvpts@}% \cv@restore\afterCURVE@\POS} \xylet@\endcurve=\endxycurve@ \DOCMODE) In fact this is essentially how |\controlpts| and |\convexhull| were interfaced before extensions were added to change styles. \begin{code} \def\controlpts{\afterCURVE{\xyctrlpts}% \readxycurve} \def\convexhull{\afterCURVE{\xycvxhull}% \readxycurve} \end{code} \displaycode \noindent The real work is done by |\xyctrlpts| and |\xycvxhull|. At the point when these macros are expanded the graphics state has the restored $p$ and $c$ to their original values, \ie\ the end-points of the curve. \medskip \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% These will be discarded when we are finished. \DOCMODE( \xydef@\moretobedone#1{\xywarning@{\string#1 is not completed yet}} \xydef@\notyetdone#1{\xywarning@{\string#1 is not implemented yet}} \let\moretobedone=\eat@ \let\notyetdone=\eat@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{|\controlpts|:} This macro visits in order each of the control points dropping the requisite object. The connection style is not used and the dirction does not change. For a loop-counter we use |\count@@@|. \DOCMODE( \xydef@\xyctrlpts@{\cv@end \cv@restore \def\crvconnect@{\straitconnect@}% \ifnum\crv@cnt@>\z@ \count@@@=\@ne \DN@{\loop@\xycontrolpt@{\count@@@}\relax \expandafter\xycvxhulldrop@\xycrvdrop@ \ifnum\crv@cnt@>\count@@@\advance\count@@@\@ne\repeat@ \cv@end \cv@restore }% \else\DN@{\relax}\fi \next@ } \xylet@\xyctrlpts=\xyctrlpts@ \DOCMODE) The token list |\crvpts@| stores the following information: current scope ||, location of $p$ and $c$, number of control points |\crv@cnt@| and their locations. It does this by storing |\def|s for control sequence names, depending on the scope, which expand to this information. Thus by executing |\the\crvpts@| at the right level, the information is conveniently recovered via control sequences which do not conflict with anything else, and whose names can be reconstructed. \DOCMODE( \xydef@\addtocrvpts@#1{\crvpts@=\expandafter{\the\crvpts@#1}} \DOCMODE) \DOCMODE( \xydef@\savescope@{\crvpts@={}% \edef\tmp@{\expandafter\noexpand\csname cv@0\endcsname}% \edef\tmp@{\noexpand\addtocrvpts@{\noexpand\def \expandafter\noexpand\tmp@{\Xc=\the\Xp \Yc=\the\Yp}}}\tmp@ \xydef@\savectrlptsnum@{% \advance\crv@cnt@\@ne \edef\tmp@{% \expandafter\noexpand\csname cv@\number\crv@cnt@\endcsname}% \edef\tmp@{\noexpand\addtocrvpts@{\noexpand\def \expandafter\noexpand\tmp@{\Xc=\the\Xc \Yc=\the\Yc}}}\tmp@ \advance\crv@cnt@\m@ne \edef\tmp@{\expandafter\noexpand\csname ptsnum@\endcsname}% \edef\tmp@{\noexpand\addtocrvpts@{% \noexpand\def\expandafter\noexpand\tmp@{\number\crv@cnt@}}}\tmp@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Convex Hull:} This macro visits in order each of the control points dropping the requisite object. With the previous as $p$ and the current one as $c$, a connection is set in the requisite style. For a loop-counter we use |\count@@@|. Use a group |{\expandafter\POS\xycrvconn@}| else the |\loop@| gets upset. This does not affect the size of the \XY-picture, since the dropped objects are not similarly shrouded. Also used for the trivial case where there are no control points, hence a straight line between $p$ and $c$ is what is requested. \DOCMODE( \xydef@\xycvxhull@{\cv@end \cv@restore \addtocrvpts@{\def\crvconnect@{\straitconnect@}}% \def\crvconnect@{\straitconnect@}% \ifnum\crv@cnt@>\z@ \count@@@=\@ne\relax \DN@{\loop@\xycontrolpt@{\count@@@}\relax \expandafter\xycvxhulldrop@\xycrvdrop@ {\expandafter\connect@\xycrvconn@\relax}\pfromc@ \ifnum\crv@cnt@>\count@@@\relax \advance\count@@@\@ne\repeat@ \cv@end }% \else \ifx\empty\xycrvconn@ \def\xycrvconn@{\dir{-}}\fi \DN@{\relax}\fi \next@ {{\expandafter\connect@\xycrvconn@}}% \cv@end \cv@restore } \xylet@\xycvxhull=\xycvxhull@ \xydef@\xycvxhulldrop@#1#{\drop@{#1}} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{Spline constructions} \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The control sequence |\xysplineparams@| provides a way to access a control sequence name that is unique to the current \XY-pic scope. This control sequence will be used to store the parameters which determine the spline curve. Similarly |\xysplineedges@| will be used to store the parameter values and lengths (along the squine) to the locations where the spline crosses the edge of the objects at the ends $p$ and $c$. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\savespline@{% \edef\endspline@{\endgroup \savesplineparams@ \savesplinerefs@ \Xmin=\the\Xmin\relax \Xmax=\the\Xmax\relax \Ymin=\the\Ymin\relax \Ymax=\the\Ymax\relax \ifInvisible@\noexpand\Invisible@true \else\noexpand\Invisible@false\fi }\endspline@ \edef\tmp@{\noexpand\addtocrvpts@{\savesplineparams@}}\tmp@ % \xycontrolpt@\m@ne \cv@end } \xycontrolpt@\z@ \cv@end \xydef@\savesplineparams@{% \noexpand\def\expandafter\noexpand\xysplineedges@{\xysplineedges@}} \xydef@\savesplinerefs@{% \noexpand\def\noexpand\xysplineparams@{% \expandafter\noexpand\xysplineparams@}% \noexpand\def\noexpand\xysplineedges@{% \expandafter\noexpand\xysplineedges@}% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% One new box register is required, for the object used to actually set the curve, \dots \DOCMODE( \xynew@{box}\splinebox@ \DOCMODE) \dots\ and 4 dimen registers ... \DOCMODE( \xynew@{dimen}\splineval@ \DOCMODE) \dots\ which is for tracking the natural spline parametrization; \DOCMODE( \xynew@{dimen}\splinedepth@ \DOCMODE) \dots\ which is for tracking he depth of the recursive algorithms; \DOCMODE( \xynew@{dimen}\splinetol@ \DOCMODE) \dots\ which contains the tolerance for the spline, (this can probably be shifted to an ordinary macro); \DOCMODE( \xynew@{dimen}\splinelength@ \DOCMODE) \dots\ which accumulates a measure of the length along a spline curve. (Currently this is not being used, as most things can be done satisfactorily using the spline parametrization only.) Some new conditionals are also required. These are for testing whether successive places on the squine are sufficiently close together, and whether the objects should be placed or not. \DOCMODE( \xynew@{if}\ifsplinefar@ \xynew@{if}\ifsplineplot@ % to plot points or not \DOCMODE) Provide some tracing ability, for debugging. \DOCMODE( \xydef@\splinetracing{\let\splinetrace@=\W@} \xydef@\splineignore@#1{} \xylet@\splinetrace@=\splineignore@ \DOCMODE) \medskip The parameters for the current section of the spline are stored indirectly in |\xysplineparams@|, while information concerning the places where the spline crosses the edges of objects at $p$ and $c$ is stored indirectly in |\xysplineedges|. A control sequence |\readsplineparams@| is used to extract this information. Each spline type must provide a |\getsplineparams@| which puts the information into easily usable form. \DOCMODE( \xydef@\readsplineparams@{% \expandafter\expandafter\expandafter\getsplineparams@\xysplineparams@ \expandafter\expandafter\expandafter\getsplineedges@\xysplineedges@} \DOCMODE) The edge locations are stored as $x.y$ where $x+1$ = spline-segment number and $y$ or $1-y$ = parameter value of the start, resp. finish, in the segment $x+1$. \DOCMODE( \xydef@\getsplineedges@#1;#2,#3;#4,{% \global\dimen@i=#1\global\dimen5=#2\relax \dimen@=#3\multiply\dimen@\m@ne \advance\dimen@\splinelength@ \global\dimen3=\dimen@ \dimen@=#4\relax \count@=\dimen@ \divide\count@\p@ \advance\dimen@-\count@\p@ \multiply\dimen@\m@ne \advance\dimen@ \count@\p@ \advance\dimen@\p@ \global\dimen7=\dimen@ } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Before constructing the curve we first examine the ``drop'' object, setting it in an |\xybox|. The size of this object determines the spacing of objects placed along the curve, via the {\em tolerance\/} of the spline. This determines the maximum separation of places at which objects will be dropped; \ie\ when two adjacent places would be separated by more than this amount, the spline algorithm constructs another place on the curve intermediate between these two. \noindent{\bf Note: } Alter the spline tolerance by adjusting the size of the ``drop'' object. In particular, a dotted line can be achieved by setting an empty object with non-zero size. If the size is zero then the default tolerance is used. Initially this is .4pt; it may be altered using |\splinetolerance|, where must be greater than zero, else the initial tolerance is reset. \DOCMODE( \xydef@\splinetolerance@#1{\dimen@=#1\relax \ifdim\dimen@>\z@\splinetol@=\dimen@ \else\splinetol@=.4\p@\fi} \xylet@\splinetolerance=\splinetolerance@ \splinetolerance@\z@ \xydef@\splinedefaulttol@{\splinetol@=.4\p@} \xydef@\xylowtolerance@{\splinedefaulttol@ \ifdim\splinetol@<\p@\divide\splinetol@\tw@\fi} \DOCMODE) Similarly examine the ``connect'' object. If none has been specified then there is no need to calculate the tangent direction at each place along the curve. This saves on both time and memory requirement. \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "Procedure": (1)~If |\xycrvdrop@| is empty, define |\splinedrop@| to expand to |\copy\zerodotbox@|, thus creating a curve constructed from small dots. Otherwise (2a)~set the |\splinebox@| with the specified object and (2b)~use |\copy\splinebox@| as the expansion of |\splinedrop@|. (3)~Set |\splinetol@| to be $\sqrt2\times$ maximum of width and height+depth of the box. If |\xycrvconn@| is empty~(4)~then |\splineconn@| is set to |\relax|. (5)~Otherwise it is necessary to reset the direction at each place along the curve before dropping the object specified by |\xycrvconn@|. \DOCMODE( \xywarnifdefined\splinedrop@ \xydef@\setstdsplinedrop@{% \setbox\splinebox@=\hbox\bgroup \setboxz@h{\expandafter\object\xycrvdrop@{}}% \splinetolerance\z@ \dimen@=\wd\z@ \dimen@ii=\ht\z@ \advance\dimen@ii\dp\z@ \ifdim\dimen@ii>\dimen@ \dimen@=\dimen@ii \fi \dimen@=1.4142\dimen@ \ifdim\dimen@>\splinetol@ \splinetol@=\dimen@\else\dimen@=\splinetol@\fi \dimen@=.5\wd\z@ \ht\z@=\z@ \dp\z@=\z@ \wd\z@=\z@ \kern-\dimen@ \Drop@@ \kern\dimen@ \edef\tmp@{\egroup \splinetol@=\the\splinetol@ \ifInvisible@\noexpand\Invisible@true \else\noexpand\Invisible@false\fi}\tmp@ \edef\splinedefaulttol@{\splinetol@=\the\splinetol@}% \def\splinedrop@{\copy\splinebox@}% \xywarnifdefined\splineconn@ \xydef@\stdsplineconn@{{\setsplinedir@ % %(5) \setboxz@h\bgroup\expandafter\object\xycrvconn@{}% \edef\tmp@{\egroup\Lc=\the\Lc \Rc=\the\Rc \Dc=\the\Dc \Uc=\the\Uc \noexpand\def\noexpand\Drop@@{\Drop@@}}\tmp@ \setboxz@h{\kern-\Lc \boxz@}% \ht\z@=\z@ \dp\z@=\z@ \wd\z@=\z@ \Drop@@}} \xydef@\setcrvobjects@{\setstdsplinedrop@ \def\splineconn@{\stdsplineconn@}} \DOCMODE) \DOCMODE( \xydef@\splinenear@#1#2#3#4{{\dX=#1\advance\dX-#2% \ifdim\dX<\z@ \dX=-\dX\fi \ifdim\splinetol@<\dX \aftergroup\splinefar@true \else \dY=#3\advance\dY-#4\ifdim\dY<\z@ \dY=-\dY\fi \ifdim\splinetol@<\dY\aftergroup\splinefar@true \else \xydist@\dX\dY \ifdim\splinetol@<\dimen@ \aftergroup\splinefar@true \else \ifdim\dimen@<.25\splinetol@\aftergroup\splinetooclose@\fi \fi\fi\fi}} \xydef@\splineadvance@@{\global\advance\splineval@\splinedepth@} \DOCMODE) This stops objects being placed too close together along the spline. It is necessary at the beginning of a segment with a non-zero edge. Otherwise, with a large |\splinetolerance@|, the accuracy of the place-finding mechanism results in being so deep in the tree that objects placed at every level on the way up would be much too close. \DOCMODE( \xydef@\splinetooclose@{\aftergroup\aftergroup\aftergroup\splinetooclose@@} \xydef@\splinetooclose@@{\def\splineplotpt@{\relax}} \DOCMODE) Each place along the curve is tested according to a |\splinetest@|. Normally this is for visibility according to the extent of the objects at the end-points of the curve. When an edge is crossed then the |\splinecontinue| routine is called to decide how the behaviour may change e.g. whether the recursion should continue or exit to the top of the existing tree. \DOCMODE( \xydef@\splinepoint{% {\splinetest@\ifsplineplot@\aftergroup\splinecontinue\fi}} \xydef@\splinepoint@@{% \splinetrace@{P@:(\the\Xc,\the\Yc)::\the\splineval@;\the\splinedepth@}% {\splinetest@\ifsplineplot@\relax\else\aftergroup\splinecontinue\fi}} \xydef@\splinepoint@{% {\splinetest@\ifsplineplot@\aftergroup\splinecontinue\fi}} \xydef@\splinecontinue@{\global\let\splinepoint=\splinepoint@@ \global\let\splinecontinue=\splinecontinue@@} \xydef@\splinecontinue@@{\global\let\splinepoint=\splinepoint@ \global\let\splinecontinue=\relax} \xylet@\splinecontinue=\splinecontinue@ \DOCMODE) Points where segments join lie at the top of the recursive tree. They are tested directly against the objects at the endpoints, in order to decide whether or not they are visible. \DOCMODE( \xydef@\splineknotpoint@{{\Xp=\Xc \Yp=\Yc \cv@start \the\Edgec\@ne \ifInside@\else\aftergroup\splineknotpoint@@\fi}}% \xydef@\splineknotpoint@@{{\Xp=\Xc \Yp=\Yc \cv@end \the\Edgec\@ne \ifInside@\else \aftergroup\splinepoint\fi}}% \xylet@\firstsplinepoint@=\splineknotpoint@ \xylet@\lastsplinepoint@= \splineknotpoint@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% This routine is used to find where the spline crosses the edges of the objects at $p$ and $c$. First (1)~ we set up the tests which will determine when the edge has been crossed. Then commence the scan.~(2) \DOCMODE( \xydef@\splined@@@{% \splinetrace@{finding end types}% \def\splineStarttest{{\Xp=\Xc \Yp=\Yc \cv@start \the\Edgec\@ne \ifInside@\aftergroup\splineplot@false\else\aftergroup\splineplot@true\fi}}% \def\splineEndtest{{\Xp=\Xc \Yp=\Yc \cv@end \the\Edgec\@ne \ifInside@\aftergroup\splineplot@false\else\aftergroup\splineplot@true\fi}}% \DN@{\splinescan@}\ifx\bstartPLACE@\empty\else \ifx\bstartPLACE@\relax\else\ifx\bstartPLACE@\unknown@\else \DN@{\splinescanbreak@}% \DNii@{0}\ifx\bstartPLACE@\nextii@ \DNii@{1}\ifx\bendPLACE@\nextii@ \DN@{}\fi\fi \fi\fi\fi \next@ }% %(2) \DOCMODE) (3)~Start a new group and bind some macros appropriate to finding the starting edge. (4)~start the spline algorithm, using a small tolerance. (5)~Close the group, thus removing the bindings of~(3). (6)~Record the information, which has been stored globally. (7)~Do the same for the ending edge. (8)~Clear the global information. After this, set the curve as usual.~(9) \DOCMODE( \xydef@\splinescan@{% \expandafter\def\xysplineedges@{\z@;\z@,\z@;\z@,}% \bgroup % %(3) \splinetrace@{scan for start}% \global\let\splineadvance@=\splineadvance@@ %(3) \let\spline@start=\splinefindStart@ %(3) \let\spline@end=\spline@end@@ %(3) \let\splinefinish@=\splinefinish@@ \let\splinewhich=\relax \global\splinelength@=\z@ \let\splinerec@=\splineSrec@ \edef\splineedges@{}% \expandafter\let\xysplineedges@\relax \bgroup \xylowtolerance@ \splined@@ \egroup % %(4) \bgroup % %(7) \splinetrace@{scan for end}% \global\let\splineadvance@=\splineadvance@@ \let\spline@start=\splinefindEnd@ \let\spline@end=\spline@end@@ \let\splinefinish@=\splinefinish@@ \let\splinewhich=\relax \global\splinelength@=\z@ \let\splinerec@=\splineErec@ \xylowtolerance@ \splined@@ \egroup \egroup \expandafter\edef\xysplineedges@{\splineedges@}% %(6) \global\let\splineedges@=\relax % %(8) \splinetrace@{edge params: \xysplineedges@}} \xydef@\splinescanbreak@{% \gdef\breakedges@{}% \DN@{0}\ifx\next@\bstartPLACE@ \DN@{\bgroup \def\tmp@####1;####2,####3;####4,{% \global\splineval@=####2\relax}% \expandafter\expandafter\expandafter\tmp@\xysplineedges@ \def\segmentnum@{\@ne}\splineRecordBreakValue \egroup}% \else \DN@{\bgroup \splinetrace@{scan for start}% \let\splinefbcontinue@=\breakstartcontinue@ \let\splinef@pt=\splinef@breakpt \let\splinef@end=\break@start \edef\tmp@{{\bstartPLACE@}}% \expandafter\splinealong@@\tmp@\z@+\endgroup\egroup }% \fi \next@ \DN@{1}\ifx\next@\bendPLACE@ \DN@{\bgroup \def\tmp@####1;####2,####3;####4,{% \global\splineval@=####4\relax}% \expandafter\expandafter\expandafter\tmp@\xysplineedges@ \def\segmentnum@{\@ne}\splineRecordBreakValue \egroup}% \else \DN@{\bgroup \splinetrace@{scan for end}% \let\splinefbcontinue@=\breakendcontinue@ \let\splinef@pt=\splinef@breakpt \let\splinef@end=\break@end \edef\tmp@{{\bendPLACE@}}% \expandafter\splinealong@@\tmp@\z@-\endgroup\egroup }% \fi \next@ \expandafter\def\expandafter \xybreakedges@\expandafter{\breakedges@}% %(6) \global\let\breakedges@=\relax % %(8) \splinetrace@{break params: \xybreakedges@::\bstartPLACE@--\bendPLACE@}% \expandafter\let\xysplineedges@\xybreakedges@ } \xydef@\splineSrec@{{% \splinetrace@{v=\the\splineval@ + d=\the\splinedepth@ : (\the\Xp,\the\Yp);(\the\Xc,\the\Yc):searching }% \splineStarttest \ifsplineplot@ \ifdim\splinedepth@<.0001\p@ \aftergroup\splinefinish@ \global\advance\splineval@ .5\splinedepth@ \else\aftergroup\splinedecast@ \fi \else \aftergroup\splineadvance@ \fi}} \xydef@\splinefindStart@@{\bgroup \bgroup \expandafter\expandafter\expandafter\getsplineparams@\xysplineparams@ \global\let\splineadvance@=\splineadvance@@ \global\let\splinepoint=\relax \let\splinegoal@=\splineRecordValue \global\dimen5=\z@ {\Xc=\Xp \Yc=\Yp \splinescanStarttest }%% first test initial point. \xydef@\splinefindStart@{% \expandafter\crv@cnt@\xycrvptsnum@\relax \ifnum\crv@cnt@>\tw@ \crv@cnt@=\@ne \splineplot@false \splinesegment@{\crv@cnt@}% {\splineStarttest %% test end of segment. \ifsplineplot@\aftergroup\splineplot@true\fi}% \ifsplineplot@ \else \searchforStartsegment@ \fi \splinesegment@{\crv@cnt@}% \splinetrace@{start is in segment \segmentnum@}% \else \splinesegment@\@ne \fi \splineplot@false \splinefindStart@@ \DOCMODE) This finds the first spline segment whose endpoint is beyond the edge of the object. \DOCMODE( \xydef@\searchforStartsegment@{\splineplot@true\loop@ \advance\crv@cnt@\@ne \expandafter\count@\xycrvptsnum@\relax \ifnum\count@=\crv@cnt@\expandafter\splineplot@false \else \splinesegment@{\crv@cnt@}{\splineStarttest \ifsplineplot@\aftergroup\splineplot@false \else\aftergroup\splineplot@true\fi }% \fi \ifsplineplot@\repeat@ \DOCMODE) Finding the end is similar, but the spline is searched in reverse order. \DOCMODE( \xydef@\splinefindEnd@@{\bgroup \bgroup \expandafter\expandafter\expandafter\getsplineparams@\xysplineparams@ \splinetrace@{params:\xysplineparams@}% \splinereverse@@ \splinetrace@{params:\xysplineparams@}% \global\let\splineadvance@=\splineadvance@@ \global\let\splinepoint=\splinescanEndtest \let\splinegoal@=\splineRecordValue \global\dimen5=\z@ {\Xc=\Xp \Yc=\Yp \splinescanEndtest }%% first test initial point. \xydef@\splinereverse@@{\splinereverse@@@} \xydef@\splinereverse@@@{% \def\postspline@{\dX=-\dX \dY=-\dY}% \dimen@ii=\Xc \Xc=\Xp \Xp=\dimen@ii \dimen@ii=\Yc \Yc=\Yp \Yp=\dimen@ii \global\dimen5=-\dimen5\relax \global\advance\dimen5\splinelength@ } \xydef@\postspline@{} \xydef@\splineErec@{{% \dimen@=\splineval@ \advance\dimen@\splinedepth@ % \splinetrace@{ER (\the\Xp,\the\Yp);(\the\Xc,\the\Yc);;; \the\dimen@,\the\splinedepth@}% \splineEndtest \ifsplineplot@ \ifdim\splinedepth@<.0001\p@ \aftergroup\splinefinish@ % \global\advance\splineval@ .5\splinedepth@ \else\aftergroup\splinedecast@ \fi \else \aftergroup\splineadvance@ \fi}} \xydef@\splinefindEnd@{% \expandafter\crv@cnt@\xycrvptsnum@\relax \ifnum\crv@cnt@>\tw@ \advance\crv@cnt@\m@ne \splineplot@false \splinesegment@{\crv@cnt@}% {\Xc=\Xp \Yc=\Yp \splineEndtest %% test end of segment. \ifsplineplot@\aftergroup\splineplot@true\fi }% \ifsplineplot@ \else \searchforEndsegment@ \fi \splinesegment@{\crv@cnt@}% \splinetrace@{end is in segment: \segmentnum@ }% \else \splinesegment@\@ne \fi \splineplot@false\splinefindEnd@@ \xydef@\searchforEndsegment@{\splineplot@true\loop@ \advance\crv@cnt@\m@ne \ifnum\crv@cnt@=\z@\expandafter\splineplot@false \else \splinesegment@{\crv@cnt@}{\Xc=\Xp \Yc=\Yp \splineEndtest \ifsplineplot@\aftergroup\splineplot@false \else\aftergroup\splineplot@true\fi}% \fi \ifsplineplot@\repeat@ \DOCMODE) These are the tests, to determine when the edge-point has been found. \DOCMODE( \xydef@\splinescanStarttest{% \splinetrace@{SST (\the\Xc,\the\Yc); \the\splinelength@, \the\splineval@}% {\splineplot@false\splineStarttest \ifsplineplot@\expandafter\splinefinish@\fi}} \xydef@\splinescanEndtest{% \splinetrace@{SET (\the\Xc,\the\Yc); \the\splinelength@, \the\splineval@}% {\splineplot@false\splineEndtest \ifsplineplot@\expandafter\splinefinish@\fi}} \DOCMODE) \DOCMODE( \xydef@\splineBrec@{{% \dimen@=\splineval@ \advance\dimen@\splinedepth@ % \splinetrace@{BR (\the\Xp,\the\Yp);(\the\Xc,\the\Yc);;; \the\dimen@,\the\splinedepth@}% \splineEndtest \ifsplineplot@ \aftergroup\splineadvance@ \else \ifdim\splinedepth@<.0001\p@ \aftergroup\splinefinish@ % \global\advance\splineval@ .5\splinedepth@ \else\aftergroup\splinedecast@ \fi \fi}} \xydef@\splinef@breakpt#1{% gobbles |\splinecancel| \ifdim\splineval@>\z@ \splinetrace@{found: val=\the\splineval@;(\the\Xc,\the\Yc)}% \else \splinetrace@{found: val=\the\splineval@;(\the\Xp,\the\Yp)}% \fi \splinefbcontinue@ } \xydef@\splinef@pt@@{% \ifdim\splineval@>\z@ \splinetrace@{found: val=\the\splineval@;c:(\the\Xc,\the\Yc)}% \global\dimen@i=\Xc \global\dimen3=\Yc \else \splinetrace@{found: val=\the\splineval@;p:(\the\Xp,\the\Yp)}% \fi \setsplinedir@ \global\dimen5=\dX \global\dimen7=\dY \global\splineval@=\splineval@ \aftergroup\splinefocus@ } \xydef@\breakstartcontinue@{% \splinetrace@{move to start edge, from (\the\Xc,\the\Yc): val=\the\splineval@ }% \global\let\splinetest@=\splineStarttest \global\let\splinerec@=\splineSrec@ \global\let\splinepoint=\relax \global\let\spline@end=\checkfoundSbreak@ \global\let\splinegoal@=\splineRecordBreakValue \global\let\splinefinish@=\splinefinish@@ } \xydef@\checkfoundSbreak@{% \ifnum\xycrvptsnum@<\thr@@\DN@{\egroup}% \else\DN@{\searchBreakSsegment@}\fi \next@ }% \xydef@\searchBreakSsegment@{% \crv@cnt@=\segmentnum@ \DN@{}\count@=\crv@cnt@ \DNii@{}\ifnum\splineval@=\z@ \ifnum\crv@cnt@=\@ne \ifnum\xycrvptsnum@=\@ne\count@=\z@\DNii@{\egroup}\fi \else\DN@{\egroup}\fi \else \advance\crv@cnt@\@ne \fi \ifnum\count@=\xycrvptsnum@ \DN@{\expandafter\splineRecordBreakValue\nextii@}\else \DN@{\splinesegment@{\crv@cnt@}\splined@@ \egroup}% \fi \next@ } \xydef@\checkfoundEbreak@{% \ifnum\xycrvptsnum@<\thr@@\DN@{\egroup}% \else\DN@{\searchBreakEsegment@}\fi \next@ }% \xydef@\searchBreakEsegment@{% \crv@cnt@=\segmentnum@ \DN@{}\count@=\crv@cnt@ \DNii@{}\ifnum\splineval@=\z@ \ifnum\crv@cnt@=\@ne \ifnum\xycrvptsnum@=\@ne\count@=\z@\DNii@{\egroup}\fi \else\DN@{\egroup}\fi \else \advance\crv@cnt@\m@ne \fi \ifnum\count@=\z@ \DN@{\expandafter\splineRecordBreakValue\nextii@}\else \DN@{\splinesegment@{\crv@cnt@}\splinereverse@ \readsplineparams@ \splined@@ \egroup}% \fi \next@ } \xydef@\break@start{\egroup \egroup \splinetrace@{scan for start}% \global\let\splineadvance@=\splineadvance@@ %(3) \let\spline@start=\splinefindBStart@ %(3) \let\spline@end=\egroup \let\splinefinish@=\splinefinish@@ \let\splinewhich=\relax \global\let\splineadvance@=\splineadvance@@ \global\let\splinepoint=\relax \let\splinegoal@=\splineRecordBreakValue \global\dimen5=\z@ \global\splinelength@=\z@ \let\splinerec@=\splineSrec@ \def\breakedges@{}% \bgroup \bgroup \splined@@ } \xydef@\splinefindBStart@{\bgroup \global\splineval@=\z@ } \xydef@\breakendcontinue@{% \splinetrace@{move to end edge, from (\the\Xc,\the\Yc): val=\the\splineval@ }% \global\let\splinetest@=\splineEndtest \global\let\splinerec@=\splineErec@ \global\let\splinepoint=\relax \global\let\spline@end=\checkfoundEbreak@ \global\let\splinegoal@=\splineRecordBreakValue \global\let\splinefinish@=\splinefinish@@ } \xydef@\break@end{\egroup \egroup \global\let\splineadvance@=\splineadvance@@ \let\spline@start=\splinefindBEnd@ \let\spline@end=\egroup \let\splinefinish@=\splinefinish@@ \let\splinewhich=\relax \global\let\splineadvance@=\splineadvance@@ \global\let\splinegoal@=\splineRecordBreakValue \global\splinelength@=\z@ \let\splinerec@=\splineErec@ \bgroup \splined@@ } \xydef@\splinefindBEnd@{\bgroup\bgroup \splinereverse@ \readsplineparams@ \global\splineval@=\z@ } \xydef@\checkfoundbreak@{% \ifnum\xycrvptsnum@<\thr@@\DN@{\egroup}% \else\DN@{\searchforBreaksegment@}\fi \next@ }% \xydef@\searchforBreaksegment@{% \crv@cnt@=\segmentnum@ \DN@{}\count@=\crv@cnt@ \DNii@{}\ifnum\splineval@=\z@ \ifnum\crv@cnt@=\@ne \ifnum\xycrvptsnum@=\@ne\count@=\z@\DNii@{\egroup}\fi \else\DN@{\egroup}\fi \else \advance\crv@cnt@\@ne \fi \ifnum\count@=\xycrvptsnum@ \DN@{\expandafter\splineRecordBreakValue\nextii@}\else \DN@{\egroup}\ifx\next@\nextii@ \DN@{\splinesegment@{\crv@cnt@}\splined@@ \egroup}% \else\DN@{\splinesegment@{\crv@cnt@}\splined@@ \egroup}% \fi\fi \next@ } %\xydef@\breakplotcontinue@#1{% %\W@{plotting from (\the\Xc,\the\Yc): val=\the\splineval@ }% % \edef\splinecontinue{\noexpand\setsplinetest@ % \splineval@>{\the\dimen7}{\noexpand\splinecancel@}{}% % \noexpand\splinesetting@ }\splinecontinue %\W@{edge at \the\dimen5}% % \expandafter\global\splinedefaulttol@ % \global\let\splinepoint=\splineplotpt@ % \global\let\spline@fend=\checkplotbreak@ % \DN@{#1}\DNii@{\spline@end}\ifx\next@\nextii@\DN@{\checkplotbreak@}\fi % \next@} %\xydef@\checkplotbreak@{% % \crv@cnt@=\segmentnum@ \DN@{\spline@end@}% % \ifnum\splineval@=\z@ \ifnum\crv@cnt@=\@ne % \DN@{\splinesegment@{\crv@cnt@}\splined@@ \egroup}\fi % \else \advance\crv@cnt@\@ne \fi \next@ } \DOCMODE) This appends the new information to that currently stored in the control sequence referenced by |\xysplineparams@|. It is temporarily stored globally in |\splineparams@|, to be later transferred to (the c.s. referenced by) |\xysplineparams@| when at the appropriate level of groupingl. \DOCMODE( \xydef@\splineRecordValue{% \ifx\unknown\segmentnum@\relax \else\expandafter\advance\expandafter\splineval@\segmentnum@\p@ \advance\splineval@-\p@ \fi \xdef\splineedges@{\splineedges@\the\splinelength@;\the\splineval@,}% \splinetrace@{found edge: \splineedges@}} \xydef@\splineRecordBreakValue{% \ifx\unknown\segmentnum@\relax \else\expandafter\advance\expandafter\splineval@\segmentnum@\p@ \advance\splineval@-\p@ \fi \xdef\breakedges@{\breakedges@\the\splinelength@;\the\splineval@,}% \global\let\spline@end=\egroup \splinetrace@{found break edge: \breakedges@}} \xydef@\splineRecordBreakEValue{% \ifdim\splineval@>\z@ \splineval@=-\splineval@ \advance\splineval@\p@ \fi \splineRecordBreakValue }% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Use the following distance approximation: dist = \cases{ dX + .5 dY/dX dY & if $dY/dX dY \lt (\sqrt2-1)\times dX $\cr {3\sqrt2\over4} dX + {\sqrt2\over4} dY/dX dY & if $(\sqrt2-1)\times dX \lt dY/dX dY \lt dX $\cr and similarly, interchanging $dY$ and $dX$ when $dY \gt dX$. $\sqrt2 \approx 1.41422$,\ $(\sqrt2)/2 \approx 0.70711$,\ $(\sqrt2)/4 \approx 0.353555$,\ $3(\sqrt2)/4 \approx 1.060665$ \DOCMODE( \xydef@\xydist@#1#2{\ifdim#1>#2\relax \quotient@@\tmp@#2#1\expandafter\dimen@ii\tmp@#2\relax \ifdim\dimen@ii>.41422#1\relax \dimen@=1.060665#1\advance\dimen@ .353555\dimen@ii\relax \else \dimen@=#1\advance\dimen@.5\dimen@ii\fi \else \quotient@@\tmp@#1#2\expandafter\dimen@ii\tmp@#1\relax \ifdim\dimen@ii>.41422#2\relax \dimen@=1.060665#2\advance\dimen@ .353555\dimen@ii\relax \else \dimen@=#2\advance\dimen@.5\dimen@ii\fi \fi} \DOCMODE) \DOCMODE( \xydef@\splineDadvance@{\splineadvance@@ \expandafter\splineDadvance@@\splineDadvance@@@} \xydef@\splineDadvance@@#1#2#3#4{% {\dX=#1\advance\dX-#2\ifdim\dX<\z@ \dX=-\dX\fi \dY=#3\advance\dY-#4\ifdim\dY<\z@ \dY=-\dY\fi \xydist@\dX\dY %\splinetrace@{(\the\dX,\the\dY):\the\dimen@}% \global\advance\splinelength@\dimen@ \DOCMODE) \DOCMODE( \xydef@\spline@end@{\lastsplinepoint@ \egroup \egroup \ht\z@=\z@ \dp\z@=\z@ \wdz@=\z@ \boxz@}% \gdef\splinetest@{\splineplot@true} \xydef@\spline@end@@{\egroup\egroup} \DOCMODE) ...value for |\splinerec@| for the fast find... \DOCMODE( \xydef@\splinefrec@{{% \advance\splineval@\splinedepth@ \splinetrace@{F:(\the\Xp,\the\Yp);(\the\Xc,\the\Yc);; \the\splineval@;\the\splinedepth@}% \ifdim\dimen5=\splineval@ \aftergroup\splinefinishf@ \else \dimen@=\dimen5\advance\dimen@-\splineval@ \ifdim\dimen@<\z@ \dimen@=-\dimen@ \fi \ifdim\dimen@<.0001\p@ \aftergroup\splinefinishf@ \else \ifdim\dimen5<\splineval@\aftergroup\splinedecast@ \else \aftergroup\splineadvance@ \fi\fi\fi}} \xydef@\splinefinishf@{\global\advance\splineval@\splinedepth@ \splinefinish@}% \DOCMODE) The macro |\spline@find#1#2| attempts to finds a specific point on a single spline segment. |#1| is the parameter value, in the range [0,1] on that segment; |#2| is a denoting how much further to slide along the segment. It really only checks whether |#2| has a negative value before passing the information to |\spline@@find#1#2|, which does the actual search. If |#2| is negative then the spline segment is searched in the reverse direction, starting at its endpoint. This is done by reversing the order of the control points, hence the parameter value found as $x$ implies that we really want $1-x$ on the un-reversed segment. Similarly the resulting vaules for |\dX| and |\dY| must be negated. The instructions to do this are loaded into a macro |\postfind@| which is expanded once the search has been completed. For an unreversed segment |\postfind@| expands to |{}|. \DOCMODE( \xydef@\spline@find#1#2{% \readsplineparams@ \global\let\splinefinish@=\splinefinish@@ \global\let\splinegoal@=\splinef@pt \global\let\splinepoint=\relax \dimen@=#1\relax\dimen@ii=#2\relax \ifdim\dimen@ii=\z@ \def\postfind@{}% \else\def\postfind@{}% \ifdim\dimen@ii<\z@ \splinereverse@ \readsplineparams@ \splinetrace@{reverse orientation: (\the\Xp,\the\Yp),(\the\Xc,\the\Yc)}% \dimen@=#1\relax\dimen@ii=#2\relax \multiply\dimen@\m@ne \advance\dimen@\p@ \multiply\dimen@ii\m@ne \def\postfind@{\dX=-\the\dX \dY=-\the\dY \noexpand\reversesplineval@}% \fi\fi \expandafter\splinetrace@\expandafter{\xysplineparams@}% \edef\next@{\noexpand\spline@@find{\the\dimen@}{\the\dimen@ii}}% \next@ } \xydef@\reversesplineval@{\splineval@=-\splineval@ \advance\splineval@\p@ } \DOCMODE) \DOCMODE( \xydef@\spline@@find#1#2{% \let\splinerec@=\splinefrec@ \global\let\splineadvance@=\splineadvance@@ \dimen5=#1\relax \ifdim #2=\z@\relax \ifdim #1=\z@\relax \bgroup \let\splined@@=\spline@@knot \global\splineval@=\m@ne\p@ \else\ifdim #1=\p@\relax \bgroup \let\splined@@=\spline@@knot \global\splineval@=\z@ \else \global\let\splineadvance@=\splineadvance@@ \setsplinetest@\splineval@>{#1}{}% {{\ifdim\splineval@=\dimen5\aftergroup\splineplot@false\fi}}% \fi\fi \else \def\splineslidetest@@{% \setsplinetest@\splinelength@<{#2}{}{}}% \let\splinefinish@=\splinefcontinue@ \fi \let\spline@end=\splinef@end \let\splinepoint@=\relax \splinelength@=\z@ \def\spline@start{\bgroup\xylowtolerance@}% \splined@@ } \xydef@\spline@@knot{% \let\setsplinedir@=\setsplineknotdir@ \splinesetparams@\spline@start \global\splinedepth@=\p@ \global\dimen@i=\Xp \global\dimen3=\Yp \splinefinishf@ \spline@end \egroup } \xydef@\splinefcontinue@{% \splinetrace@{sliding... from (\the\Xc,\the\Yc): val=\the\splineval@ }% \global\let\splinerec@=\splinerec@@ \global\splinelength@=\z@ \global\splineval@=\splineval@ \global\let\splinepoint=\splinefindtest@ \global\let\splinegoal@=\splinef@pt \global\let\splineadvance@=\splineDadvance@ \global\let\splinefinish@=\splinefinish@@ \splineslidetest@@ \xydef@\splinefindtest@{% \splinetrace@{SFT (\the\Xc,\the\Yc); \the\splinelength@, \the\splineval@}% {\splineplot@false\splinetest@ \ifsplineplot@\expandafter\splinefinishf@\fi}} \xydef@\splinef@end{\edef\tmp@{\egroup\splinelength@=\the\splinelength@}\tmp@} \xydef@\splinef@pt{% \ifdim\splineval@>\z@ \splinetrace@{found: val=\the\splineval@;c:(\the\Xc,\the\Yc)}% \global\dimen@i=\Xc \global\dimen3=\Yc \else \splinetrace@{found: val=\the\splineval@;p:(\the\Xp,\the\Yp)}% \fi \setsplinedir@ \global\dimen5=\dX \global\dimen7=\dY \global\splineval@=\splineval@ \aftergroup\splinefocus@ } \DOCMODE) \DOCMODE( \def\setsplinetest@#1#2#3#4#5{% \DNii@{\gdef\splinetest@}% \DN@##1\next{\def\tmp@{{\splineplot@false \ifdim#1#2##1\relax #4\else\splineplot@true#5\fi}}}% \next@#3\relax\next \expandafter\nextii@\tmp@ }% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\splinefocus@{% \dX=\dimen5\relax \dY=\dimen7\relax \setsplinedir@ } \xydef@\splinesegment@#1{} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% There are some extra hooks. \DOCMODE( \xydef@\xyprecurve@{} \xydef@\xypostcurve@{} \DOCMODE) \DOCMODE( \xydef@\splineset@{% \ifx\bstartPLACE@\empty\splinetrace@{invisible curve}% \DN@{\xyprecurve@ \edef\tmp@{\egroup \Xmax=\the\Xmax \Xmin=\the\Xmin \Ymax=\the\Ymax \Ymin=\the\Ymin}\tmp@ \xypostcurve@ }% \else \DN@{\xyprecurve@ \xysplinespecialcases@ \edef\tmp@{\egroup \Xmax=\the\Xmax \Xmin=\the\Xmin \Ymax=\the\Ymax \Ymin=\the\Ymin}\tmp@ \xypostcurve@ }% \next@ } \DOCMODE) For the actual setting, provide hooks which will allow alternative back-ends to be used in the special cases. \DOCMODE( \xydef@\xysplinespecialcases@{% \ifx\empty\xycrvdrop@ \ifx\empty\xycrvconn@ \DN@{\splinesolid@}% \else \DN@{ \dir{-}}\ifx\next@\xycrvconn@ \DN@{\splinesolid@}% \else \DN@{ \dir 2{-}}\ifx\next@\xycrvconn@ \DN@{\splinedoubled@}% \else \DN@{ \dir{=}}\ifx\next@\xycrvconn@ \DN@{\splineribboned@}% \else \DN@{ \dir {2.}}\ifx\next@\xycrvconn@ \DN@{\splinedoubled@}% \else \DN@{ \dir 3{-}}\ifx\next@\xycrvconn@ \DN@{\splinetrebled@}% \else \DN@{ \dir {3.}}\ifx\next@\xycrvconn@ \DN@{\splinetrebled@}% \else \DN@{ \dir{--}}\ifx\next@\xycrvconn@ \DN@{\splinedashed@}% \else \DN@{ \dir{.}}\ifx\next@\xycrvconn@ \DN@{\splinedotted@}% \else \DN@{ \dir{:}}\ifx\next@\xycrvconn@ \DN@{\splinedbldotted@}% \else \ifdim\splinetol@>\z@ \else \splinetolerance\z@ \fi \DN@{\splineset@@}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \else \DN@{\splineset@@}% \fi \next@} \DOCMODE) The special cases are handled just like the normal case except However the control sequence names provide a place for rebinding to accomodate alternative back-ends. \DOCMODE( \xydef@\splinesolid@{\setbox\splinebox@=\zerodot \def\xycrvdrop@{ }% \def\xycrvconn@{}\splineset@@} \xydef@\splinedoubled@{\splinetolerance\z@\def\xycrvdrop@{ }\def\splinedrop@{}% \def\xycrvconn@{\dir2{.}}\splineset@@} \xydef@\splineribboned@{\splinetolerance\z@\def\xycrvdrop@{ }\def\splinedrop@{}% \def\xycrvconn@{\dir{:}}\splineset@@} \xydef@\splinetrebled@{\splinetolerance\z@\def\xycrvdrop@{ }\def\splinedrop@{}% \def\xycrvconn@{\dir3{.}}\splineset@@} \xydef@\splinedashed@{\splinetol@=1pc \def\xycrvdrop@{ }\def\splinedrop@{}% \def\xycrvconn@{\dir{-}}\splineset@@} \xydef@\splinedotted@{\setbox\splinebox@=\zerodot \def\xycrvdrop@{ }% \splinetol@=\jot \def\xycrvconn@{}\splineset@@} \xydef@\splinedbldotted@{\def\xycrvdrop@{ }\def\splinedrop@{}% \splinetol@=\jot \def\xycrvconn@{\dir{:}}\splineset@@} \DOCMODE) This establishes the test appropriate to actually setting the spline curve. Global definitions are used. This may not always be necessary!! \BUG: the |7.5pt| below should be the |\Step@@| method to be included. \DOCMODE( %%\xydef@\slowsplineset@@{% \xydef@\splineset@@{% \readsplineparams@ \ifx\xycrvdrop@\empty \splinetol@=7.5\p@ \def\splinedefaulttol@{\splinetol@=7.5\p@}\def\splinedrop@{}% \else\edef\splinedefaulttol@{\splinetol@=\the\splinetol@}\fi \ifx\xycrvconn@\empty \def\splineconn@{}\fi \splinetrace@{set the curve}% \global\let\splineadvance@=\splineadvance@@ \let\splinerec@=\splineTrec@ \def\splineStarttest{\splinetest@}% \let\splinepoint=\relax %6 \let\spline@start=\spline@start@ \let\spline@end=\spline@end@ %5 \ifdim\dimen7=\p@ \gdef\splinecontinue{\splinesetting@ \global\let\splinetest@=\splineplot@true}% \else\xdef\splinecontinue{\noexpand\setsplinetest@ \splineval@>{\the\dimen7}{\noexpand\splinebreakcancel@}{}% \noexpand\splinesetting@ }\fi \ifdim\dimen5=\z@ \DN@{\splinesetting@\splinecontinue}% \else\edef\next@{\noexpand\setsplinetest@ \splineval@<{\the\dimen5}{}% {\noexpand\expandafter\noexpand\splinecontinue}% \noexpand\splinesetting@ }\fi \next@ \ifdim\dimen5=\p@\DN@{}\else\DN@{\splined@@}\fi \next@ } %\xydef@\splineset@@{% % \readsplineparams@ % \ifx\xycrvdrop@\empty \splinetol@=7.5\p@ % \def\splinedefaulttol@{\splinetol@=7.5\p@}\def\splinedrop@{}% % \else\edef\splinedefaulttol@{\splinetol@=\the\splinetol@}\fi % \ifx\xycrvconn@\empty \def\splineconn@{}\fi %\splinetrace@{set the curve at (\the\Xp,\the\Yp)--(\the\Xc,\the\Yc)}% % \kern\Xp % \global\dimen@i=\Xc \global\dimen3=\Yc % \global\let\splinefinish@=\breakplotcontinue@ % \let\spline@end=\checkplotbreak@ % \dimen@=\dimen5\relax\divide\dimen@\p@ % \count@=\dimen@ \dimen@ii=\count@\p@ % \ifdim\dimen5=\dimen@ii \ifdim\dimen5=\z@\count@=\@ne\fi % \else\advance\count@\@ne\fi % \expandafter\splinesegment@\expandafter{\the\count@}% % \count@=\segmentnum@ \advance\count@\m@ne % \dimen@=\dimen5\advance\dimen@-\count@\p@ % \expandafter\spline@@find\expandafter{\the\dimen@}\z@ } \DOCMODE) |\splineTrec@| is the initial value for |\splinerec@| when a spline is being set. It descends the tree of places on the spline (1) until the required parameter value is found (2a) or is sufficiently close (2b). At this point call |\splinesetting@| and |\splinecontinue| to setup, and proceed with, the actual type-setting. \DOCMODE( \xydef@\splineTrec@{{% \advance\splineval@\splinedepth@ % (1) \ifdim\dimen5=\splineval@ % (2a) \aftergroup\splinecontinue % (3) \else \dimen@=\dimen5\advance\dimen@-\splineval@ \ifdim\dimen@<\z@ \dimen@=-\dimen@ \fi \ifdim\dimen@<.001\p@ % possibly too high ? % (2b) \aftergroup\splinecontinue % (3) \else \ifdim\dimen5<\splineval@\aftergroup\splinedecast@ % (1) \else \aftergroup\splineadvance@ % (1) \fi\fi\fi}} \DOCMODE) Setting the curve uses the current |\splinerec@@| and sets |\splinepoint| to |\splineplotpt@| to cause objects to be typeset. This must be done via a |\gdef| rather than a |\global\let| since it is sometimes necessary to omit the object when it would be too close, see |\splinetooclose@|. \DOCMODE( \xydef@\splinesetting@{\xyFN@\splinesetting@@} \xydef@\splinesetting@@{% \global\let\splinerec@=\splinerec@@ \gdef\splinepoint{\splineplotpt@}} \DOCMODE) This handles the placement of the objects at each point of the curve. \DOCMODE( \xydef@\splineplotpt@{{\global\advance\dimen@i-\Xc \splinetest@ \ifsplineplot@ \ifdim\dimen@i<\z@ \raise\Yc\hbox to-\dimen@i{\hfill\splinedrop@}% \else \kern-\dimen@i\raise\Yc\hbox{\splinedrop@}\fi \ifx\splineconn@\relax\else\raise\Yc\hbox{% \setboxz@h{\splineconn@}\Drop@@}\fi \splinetrace@{P:(\the\Xc,\the\Yc);;\the\dimen@i;\the\dimen3% ,\the\splinelength@,\the\splineval@,\the\splinedepth@}% \else \splinetrace@{N:(\the\Xc,\the\Yc);;\the\dimen@i;\the\dimen3% ,\the\splinelength@,\the\splineval@,\the\splinedepth@}% \hglue-\dimen@i \fi \global\dimen@i=\Xc \global\dimen3=\Yc \DOCMODE) These handle the ``cleaning up'' after a point on the curve has been located. \DOCMODE( \xydef@\splinefinish@@{\postspline@ \splinegoal@ \splinecancel@ } \xydef@\splinecancel@{% \global\let\splinerec@=\relax \global\let\splinepoint=\relax \global\let\splinecontinue=\relax \xydef@\splinebreakcancel@{\bsplinecancel@ \splinecancel@} \xydef@\bsplinecancel@{\gdef\lastbspline@{\leave@\leave@}% \global\let\middlebspline@=\lastbspline@ } \xydef@\splinegoal@{\ifdim\splineval@>\z@ \global\dimen@i=\Xc \global\dimen3=\Yc \fi \global\splineval@=\splineval@ \global\splinelength@=\splinelength@ \setsplineTangent@ } \xylet@\splinefinish@\splinefinish@@ \xylet@\splinewhich\splineDwhich@ \xydef@\squinewhich@{% \ifdim\dimen5>\squinelength % \Xc=\Lc \Yc=\Dc \dX=\Xc \dY=\Yc \advance\dX-\Lc \advance\dY-\Uc \advance\squineval-\squinedepth \else \Xc=\Xp \Yc=\Yp \advance\squineval-\squinedepth \dX=\Lc \dY=\Uc \advance\dX-\Xp \advance\dY-\Yp \fi \dX=-\dX \dY=-\dY %%% hack which works. \squinefinish} \xydef@\squineDwhich@{% \ifdim\dimen5>\squinelength % \Xc=\Lc \Yc=\Dc \dX=\Xc \dY=\Yc \advance\dX-\Lc \advance\dY-\Uc \else \Xc=\Xp \Yc=\Yp \advance\squineval-\squinedepth \dX=\Lc \dY=\Uc \advance\dX-\Xp \advance\dY-\Yp \fi \dX=-\dX \dY=-\dY %%% hack which works. \squinefinish} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{B\'ezier quadratic splines --- squines} \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\xyquadbezier@{% \global\let\splinerec@=\relax \addtocrvpts@{\def\crvconnect@{\squineconnect@}}% \xyquadmethods@ \let\splinesegment@=\squinesegment@ \begingroup\setcrvobjects@ \xysplineparams@ \splined@@@ \bgroup \setupsquine@ \squined@ \splineset@ \savespline@ } \xydef@\xyquadmethods@{% \def\crvconnect@{\squineconnect@}% \def\splinereverse@@{\splinereverse@@@}% \let\getsplineparams@=\getsquineparams@ \let\spline@start@=\squine@start@ \let\splinedecast@=\squinedecast@ \let\splinerec@@=\squinerec@ \let\splined@@=\squined@@ \let\splineinfo@=\squineinfo@ \let\setsplinedir@=\setsquinedir@ \let\setsplineknotdir@=\setsquineknotdir@ \let\splinereverse@=\squinereverse@ \let\splineDadvance@@@=\squineDadvance@@@ \let\splinesetparams@=\squinesetparams@ \xydef@\squineconnect@{\splineconnect@ \crvconnect@@ \let\splinesegment@=\squinesegment@ }% \xydef@\squinesegment@#1{\xyquadmethods@ \def\segmentnum@{\@ne}% \setupsquine@ \squined@ } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Use the registers |\A@| and |\B@| to store the coordinates of the single control point. The whole curve lies within the convex polygon with vertices at $p$, $(p+a)/2$, $(c+a)/2$ and $c$ where $a$ denotes the control point. Set |\Xmax|, |\Xmin|, |\Ymax| and |\Ymin| to be the extremes of the coordinates of these 4 points. There may not actually be any point on the curve achieving these extremes, but certainly we get pretty close. \DOCMODE( \xydef@\setupsquine@{% \xycontrolpt@\z@ \Xp=\Xc \Yp=\Yc \xycontrolpt@\@ne \A@=\Xc \B@=\Yc \xycontrolpt@\tw@ \ifdim \A@>\Xmax \advance\Xmax\A@ \divide\Xmax\tw@ \else \ifdim \A@<\Xmin \advance\Xmin\A@ \divide\Xmax\tw@ \fi\fi \ifdim \B@>\Ymax \advance\Ymax\B@ \divide\Ymax\tw@ \else \ifdim \B@<\Ymin \advance\Ymin\B@ \divide\Ymax\tw@ \fi\fi } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The algorithm used for computing coordinates of points on quadratic B\'ezier splines is essentially that used by D.E.Knuth in |picmac.tex|\cite{picmac} (aka |gpxmac.tex|). It is a recursive de Casteljau algorithm of the ``divide and conquer'' type. (In earlier versions (not released) of \XY-pic, these types of curves were given the name ``squines''. This explains some of the control sequence names used here.) The differences from Knuth's algorithm are simply to allow more of the available information to be used at points along the spline. In particular the tangent direction can be calculated and tests can be performed to decide when to break out of the algorithm, rather than letting it run its full course. \DOCMODE( \xydef@\squinedecast@{\divide\splinedepth@\tw@ \Rc=\Lc \advance\Lc\Xp \divide\Lc\tw@ \advance\Rc\Xc \divide\Rc\tw@ \A@=\Lc \advance\A@\Rc \divide\A@\tw@ \Dc=\Uc \advance\Uc\Yp \divide\Uc\tw@ \advance\Dc\Yc \divide\Dc\tw@ \B@=\Uc \advance\B@\Dc \divide\B@\tw@ \begingroup \Xc=\A@ \Yc=\B@ \splinerec@ \endgroup \begingroup \Xc=\A@ \Yc=\B@ \splinepoint \endgroup \Xp=\A@ \Lc=\Rc \Yp=\B@ \Uc=\Dc \splinerec@} \xydef@\squinerec@{{\splinefar@false \splinenear@\Xp\Xc\Yp\Yc \ifsplinefar@ \aftergroup\squinedecast@ \else \aftergroup\splineadvance@ \fi}} \xydef@\squineDadvance@@@{\Xp\Lc\Yp\Dc} \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The tangent direction is computed from the displacement to the ``recursive'' control point. \DOCMODE( \xydef@\setsquinedir@{% \dX=\Xc \advance\dX-\Lc \dY=\Yc \advance\dY-\Uc \dimen@=\ifdim\dX<\z@-\fi\dX \ifdim\dimen@<.02\p@ \dimen@=\ifdim\dY<\z@-\fi\dY \ifdim\dimen@<.02\p@ \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \fi\fi \ifdim\Xc=\Xp \ifdim\Yc=\Yp \dX=\Lc \advance\dX-\Xp \dY=\Uc \advance\dY-\Yp \fi\fi \setupDirection@ii } \xydef@\setsquineknotdir@{% \ifdim\splineval@=\z@ \expandafter \setsquinezerodir@ \else\expandafter \setsquinedir@ \fi } \xydef@\setsquinezerodir@{% \dX=\Lc \advance\dX-\Xp \dY=\Uc \advance\dY-\Yp \ifdim\dX=\z@ \relax \ifdim\dY=\z@ \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \fi\fi \setupDirection@ii } \DOCMODE) We need a way to access the information in |\splineparams|. \DOCMODE( \xydef@\getsquineparams@#1,#2,#3,#4,#5,#6,#7,{% \splinelength@=#1\relax\Xp=#2\relax\Yp=#3\relax \A@=#4\relax\B@=#5\relax\Xc=#6\relax\Yc=#7\relax \xydef@\squineinfo@{% \expandafter\removePT@\the\Xc\space \expandafter\removePT@\the\Yc\space \expandafter\removePT@\the\A@\space \expandafter\removePT@\the\B@\space \expandafter\removePT@\the\Xp\space \expandafter\removePT@\the\Yp\space \expandafter\removePT@\the\dimen5\space\space \expandafter\removePT@\the\dimen7\space \xydef@\squinereverse@{\readsplineparams@ \expandafter\edef\xysplineparams@{\the\splinelength@,% \the\Xc,\the\Yc,\the\A@,\the\B@,\the\Xp,\the\Yp,}}% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Start the picture by (1)~setting a box, as usual. (2)~Initialize global variables; these must be global since they are used to store information which must be preserved outside the grouping which is vital to the recursive nature of the algorithm. (3)~Move horizontally to the starting point at $p$. (4)~Begin by handling the starting point; \ie\ decide whether it is to be plotted or not. \DOCMODE( \xydef@\squine@start@{\setboxz@h\bgroup % %(1) \global\splinelength@=\z@ \global\dimen@i=\z@ \global\dimen3=\z@ % %(2) \kern\Xp % %(3) {\squinesetparams@ \Xc=\Xp \Yc=\Yp \firstsplinepoint@ % %(4) }\bgroup } \xydef@\squinesetparams@{% \global\dimen@i=\Xp \global\dimen3=\Yp \Lc=\A@ \Uc=\B@ \Rc=\Lc \Dc=\Uc }% \xydef@\squined@{% \expandafter\edef\xysplineparams@{% \the\z@,\the\Xp,\the\Yp,\the\A@,\the\B@,\the\Xc,\the\Yc,}} \xydef@\squined@@{% \global\splinedepth@=\p@ \global\splineval@=\z@ \global\dimen@i=\Xp \global\dimen3=\Yp \spline@start %\splinetracing \ifx\splinerec@\relax \let\splinerec@=\squinerec@\fi \Lc=\A@ \Uc=\B@ \Rc=\Lc \Dc=\Uc \splinerec@ \spline@end } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{B\'ezier cubic splines} \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DOCMODE( \xydef@\xycubicbezier@{% \addtocrvpts@{\def\crvconnect@{\cubicconnect@}}% \xycubicmethods@ \let\splinesegment@=\cubicsegment@ \begingroup \setcrvobjects@ \xysplineparams@ \splined@@@ \bgroup \setupcubic@ \cubiced@ \splineset@ \savespline@ } \xydef@\xycubicmethods@{% \def\crvconnect@{\cubicconnect@}% \def\splinereverse@@{\cubicreverse@@}% \let\getsplineparams@=\getcubicparams@ \let\spline@start@=\cubic@start@ \let\splinedecast@=\cubicdecast@ \let\splinerec@@=\cubicrec@ \let\splined@@=\cubiced@@ \let\splineinfo@=\cubicinfo@ \let\setsplinedir@=\setcubicdir@ \let\setsplineknotdir@=\setcubicknotdir@ \let\splinereverse@=\cubicreverse@ \let\splineDadvance@@@=\cubicDadvance@@@ \let\splinesetparams@=\cubicsetparams@ \xydef@\cubicconnect@{\splineconnect@ \crvconnect@@ \let\splinesegment@=\cubicsegment@ }% \xydef@\cubicsegment@#1{\xycubicmethods@ \def\segmentnum@{\@ne}% \setupcubic@ \cubiced@ } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Use the registers |\A@|, |\B@|, |\dimen3| and |\dimen5| to store coordinates of the two control points, denoted $l$ and $r$ say. The whole curve lies within the convex polygon $p$, $(p+l)/2$, $(c+r)/2$ and $c$ where the vertices are not necessarily in this order. Set |\Xmax|, |\Xmin|, |\Ymax| and |\Ymin| to be the extremes of the coordinates of these 4 points. There may not actually be any point on the curve achieving these extremes, but certainly we get pretty close. \DOCMODE( \xydef@\setupcubic@{% \xycontrolpt@\z@ \Xp=\Xc \Yp=\Yc \xycontrolpt@\@ne \A@=\Xc \B@=\Yc \xycontrolpt@\tw@ \global\dimen3=\Xc \global\dimen5=\Yc \xycontrolpt@\thr@@ \adjustmaxmin@ } \xydef@\adjustmaxmin@{% \ifdim \A@>\Xmax \dimen@=\A@ \advance\dimen@\Xp \divide\dimen@\tw@ \ifdim\dimen@>\Xmax \Xmax=\dimen@ \fi \else \ifdim \A@<\Xmin \dimen@=\A@ \advance\dimen@\Xp \divide\dimen@\tw@ \ifdim\dimen@<\Xmin \Xmin=\dimen@ \fi \fi\fi \ifdim \B@>\Ymax \dimen@=\B@ \advance\dimen@\Yp \divide\dimen@\tw@ \ifdim\dimen@>\Ymax \Ymax=\dimen@ \fi \else \ifdim \B@<\Ymin \dimen@=\B@ \advance\dimen@\Yp \divide\dimen@\tw@ \ifdim\dimen@<\Ymin \Ymin=\dimen@ \fi \fi\fi \ifdim \dimen3>\Xmax \dimen@=\dimen3\advance\dimen@\Xc \divide\dimen@\tw@ \ifdim\dimen@>\Xmax \Xmax=\dimen@ \fi \else \ifdim \dimen3<\Xmin \dimen@=\dimen3\advance\dimen@\Xc \divide\dimen@\tw@ \ifdim\dimen@<\Xmin \Xmin=\dimen@ \fi \fi\fi \ifdim \dimen5>\Ymax \dimen@=\dimen5\advance\dimen@\Yc \divide\dimen@\tw@ \ifdim\dimen@>\Ymax \Ymax=\dimen@ \fi \else \ifdim \dimen5<\Ymin \dimen@=\dimen5\advance\dimen@\Yc \divide\dimen@\tw@ \ifdim\dimen@<\Ymin \Ymin=\dimen@ \fi \fi\fi \dimen@=\A@ \advance\dimen@\dimen3\divide\dimen@\tw@ \ifdim\dimen@>\Xmax \Xmax=\dimen@ \else \ifdim\dimen@<\Xmin \Xmin=\dimen@ \fi\fi \dimen@=\B@ \advance\dimen@\dimen5\divide\dimen@\tw@ \ifdim\dimen@>\Ymax \Ymax=\dimen@ \else \ifdim\dimen@<\Ymin \Ymin=\dimen@ \fi\fi \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The recursive algorithm for cubic B\'ezier splines is similar to the quadratic one. Now there are two ``recursive'' control points to be calculated upon each subdivision. On the $p$-side: \begin{eqnarray*} p_p &=& p \\ l_p &=& (p+l)/2 \\ r_p &=& (p+2l+r)/4 \\ c_p &=& (p+3l+3r+c)/8 \\ \end{eqnarray*} while on the $c$-side \begin{eqnarray*} p_c &=& (p+3l+3r+c)/8 \\ l_c &=& (l+2r+c)/4 \\ r_c &=& (r+c)/2 \\ c_c &=& c \\ \end{eqnarray*} Notice that $c_p=p_c$ and that the tangents match there. \DOCMODE( \xydef@\cubicdecast@{\divide\splinedepth@\tw@ % \A@=\Lc \advance\A@\Rc \divide\A@\tw@ \advance\Rc\Xc \divide\Rc\tw@ \B@=\Uc \advance\B@\Dc \divide\B@\tw@ \advance\Dc\Yc \divide\Dc\tw@ \advance\Lc\Xp \divide\Lc\tw@ \advance\Uc\Yp \divide\Uc\tw@ \begingroup \Xc=\Rc \advance\Xc\Lc \divide\Xc\tw@ \advance\Xc\A@ \divide\Xc\tw@ \Yc=\Dc \advance\Yc\Uc \divide\Yc\tw@ \advance\Yc\B@ \divide\Yc\tw@ \Rc=\Lc \advance\Rc\A@ \divide\Rc\tw@ \Dc=\Uc \advance\Dc\B@ \divide\Dc\tw@ \bgroup \splinerec@ \egroup \splinepoint \endgroup \Xp=\Lc \advance\Xp\Rc \divide\Xp\tw@ \advance\Xp\A@ \divide\Xp\tw@ \Lc=\Rc \advance\Lc\A@ \divide\Lc\tw@ \Yp=\Uc \advance\Yp\Dc \divide\Yp\tw@ \advance\Yp\B@ \divide\Yp\tw@ \Uc=\Dc \advance\Uc\B@ \divide\Uc\tw@ \splinerec@ } \xydef@\cubicrec@{{\splinefar@false \splinenear@\Xp\Xc\Yp\Yc \ifsplinefar@ \aftergroup\cubicdecast@ \else \aftergroup\splineadvance@ \fi}} \xydef@\cubicDadvance@@@{\Xp\Xc\Yp\Yc} \DOCMODE) \DOCMODE( \xydef@\cubiced@@{% \global\splinedepth@=\p@ \global\splineval@=\z@ \spline@start \global\dimen@i=\Xp \global\dimen3=\Yp \splinerec@ \spline@end } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The tangent direction is computed from the displacement to the ``recursive'' control point. \DOCMODE( \xydef@\setcubicdir@{% \ifdim\splinedepth@<.001\p@\DN@{\cubiccoarsedir@}% \else\DN@{\cubicfinedir@}\fi \next@ } \xydef@\cubicfinedir@{% \dX=\Xc \advance\dX-\Rc \dY=\Yc \advance\dY-\Dc \ifdim\dX=\z@ \ifdim\dY=\z@ \dX=\Xc \advance\dX-\Lc \dY=\Yc \advance\dY-\Uc \ifdim\dX=\z@ \ifdim\dY=\z@ \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \fi\fi \fi\fi \ifdim\Xc=\Xp \ifdim\Yc=\Yp \dX=\Lc \dY=\Uc \advance\dX-\Xp \advance\dY-\Yp \ifdim\dX=\z@ \ifdim\dY=\z@ \dX=\Rc \dY=\Dc \advance\dX-\Xp \advance\dY-\Yp \fi\fi \fi\fi \setupDirection@ii \splinetrace@{dir:(\the\dX,\the\dY),\the\Direction; depth:\the\splinedepth@}} \xydef@\cubiccoarsedir@{% \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \setupDirection@ii \global\dimen5=\dX \global\dimen7=\dY \splinetrace@{dir:(\the\dX,\the\dY),\the\Direction; depth:\the\splinedepth@}} \xydef@\setcubicknotdir@{% \ifdim\splineval@=\z@ \expandafter \setcubiczerodir@ \else\expandafter \setcubicdir@ \fi } \xydef@\setcubiczerodir@{% \dX=\Lc \advance\dX-\Xp \dY=\Uc \advance\dY-\Yp \ifdim\dX=\z@ \relax \ifdim\dY=\z@ \dX=\Rc \advance\dX-\Xp \dY=\Dc \advance\dY-\Yp \ifdim\dX=\z@ \relax \ifdim\dY=\z@ \dX=\Xc \advance\dX-\Xp \dY=\Yc \advance\dY-\Yp \fi\fi \fi\fi \setupDirection@ii } \DOCMODE) We need a way to access the information in |\splineparams|. \DOCMODE( \xydef@\getcubicparams@#1,#2,#3,#4,#5,#6,#7,#8,#9,{% \splinelength@=#1\relax\Xp=#2\relax\Yp=#3\relax\Lc=#4\relax \Uc=#5\relax\Rc=#6\relax\Dc=#7\relax\Xc=#8\relax\Yc=#9\relax \xydef@\cubicinfo@{% \expandafter\removePT@\the\Xp\space \expandafter\removePT@\the\Yp\space \expandafter\removePT@\the\Lc\space \expandafter\removePT@\the\Uc\space \expandafter\removePT@\the\Rc\space \expandafter\removePT@\the\Dc\space \expandafter\removePT@\the\Xc\space \expandafter\removePT@\the\Yc\space \expandafter\removePT@\the\dimen5\space\space \expandafter\removePT@\the\dimen7\space \xydef@\cubicreverse@{\readsplineparams@ \expandafter\edef\xysplineparams@{\the\splinelength@,% \the\Xc,\the\Yc,\the\Rc,\the\Dc,\the\Lc,\the\Uc,\the\Xp,\the\Yp,}}% \xydef@\cubicreverse@@{% \splinereverse@@@ \dimen@ii=\Lc \Lc=\Rc \Rc=\dimen@ii \dimen@ii=\Uc \Uc=\Dc \Dc=\dimen@ii } \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Start the picture by (1)~setting a box, as usual. (2)~Initialize global variables; these must be global since they are used to store information which must be preserved outside the grouping which is vital to the recursive nature of the algorithm. (3)~Move horizontally to the starting point at $p$. (4)~Begin by handling the starting point; \ie\ decide whether it is to be plotted or not. \DOCMODE( \xydef@\cubic@start@{\setboxz@h\bgroup % %(1) \global\splinelength@=\z@ \global\dimen@i=\z@ \global\dimen3=\z@ % %(2) \kern\Xp % %(3) {\cubicsetparams@ \Xc=\Xp \Yc=\Yp \Rc=\Xc \Dc=\Yc \firstsplinepoint@ }\bgroup } % %(4) \xydef@\cubicsetparams@{% \expandafter\expandafter\expandafter\getsplineparams@\xysplineparams@ \global\dimen@i=\Xp \global\dimen3=\Yp } \DOCMODE) \DOCMODE( \xydef@\cubiced@{% \expandafter\edef\xysplineparams@{\the\z@,\the\Xp,\the\Yp ,\the\A@,\the\B@,\the\dimen3,\the\dimen5,\the\Xc,\the\Yc,}% \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsubsection{B-splines} \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The cases of 3 and 4 control points have some special simplifying features. \DOCMODE( \xydef@\xybspline@iii{% \addtocrvpts@{\def\crvconnect@{\bsplineiiiconnect@}}% \def\crvconnect@{\bsplineiiiconnect@}\xybsplinemethods@ \let\splinesegment@=\bsplinesegment@iii \begingroup\setcrvobjects@ \dobspline@ \savespline@ } \xydef@\bsplineiiiconnect@{\splineconnect@ \crvconnect@@ \let\splinesegment@=\bsplinesegment@iii }% \xydef@\xybspline@iv{% \addtocrvpts@{\def\crvconnect@{\bsplineivconnect@}}% \def\crvconnect@{\bsplineivconnect@}\xybsplinemethods@ \let\splinesegment@=\bsplinesegment@iv \begingroup\setcrvobjects@ \dobspline@ \savespline@ } \xydef@\bsplineivconnect@{\splineconnect@ \crvconnect@@ \let\splinesegment@=\bsplinesegment@iv }% \DOCMODE) Now for the general case of $\ge5$ control points. \DOCMODE( \xydef@\xybspline@{% \splinetrace@{B-spline with \numcontrolpts\space control points.}% \addtocrvpts@{\def\crvconnect@{\bsplineconnect@}}% \def\crvconnect@{\bsplineconnect@}\xybsplinemethods@ \let\splinesegment@=\bsplinesegment@ \begingroup\setcrvobjects@ \dobspline@ \savespline@ } \xydef@\xybsplinemethods@{% \def\splinereverse@@{\cubicreverse@@}% \let\getsplineparams@=\getbsplineparams@ \let\spline@start@=\cubic@start@ \let\splinedecast@=\cubicdecast@ \let\splinerec@@=\cubicrec@ % \let\splined@@=\cubiced@@ \let\splined@@=\bsplined@@ \let\splineinfo@=\cubicinfo@ \let\setsplinedir@=\setcubicdir@ \let\setsplineknotdir@=\setcubicknotdir@ \let\splinereverse@=\cubicreverse@ \let\splineDadvance@@@=\cubicDadvance@@@ \let\splinesetparams@=\cubicsetparams@ \global\let\lastbspline@=\lastbspline@@ \global\let\middlebspline@=\middlebspline@@ \xydef@\bsplined@@{% \global\splinedepth@=\p@ \global\splineval@=\z@ \spline@start \global\dimen@i=\Xp \global\dimen3=\Yp \splinerec@ \spline@end } \DOCMODE) \DOCMODE( \xydef@\bsplineconnect@{\splineconnect@ \crvconnect@@ \let\splinesegment@=\bsplinesegment@ }% \xydef@\getbsplineparams@{\getcubicparams@} \xydef@\dobspline@{\xysplineparams@ \scanbspline@ \firstbspline@ }% \DOCMODE) Registers |\dimen3| and |\dimen5| are used globally, since the standard local registers are already used. \DOCMODE( \xydef@\firstbspline@{% \enter@{\pfromthep@ \cfromthec@}\enter@{\cfromthec@}\bgroup \def\segmentnum@{1}\xycontrolpt@\z@ \Xp=\Xc \Yp=\Yc \splinetrace@{0: \the\Xp, \the\Yp}% \xycontrolpt@\@ne \A@=\Xc \B@=\Yc \splinetrace@{1: \the\Xc, \the\Yc}% \xycontrolpt@\tw@ \splinetrace@{2: \the\Xc, \the\Yc}% \dimen@=\Xc \advance\dimen@\A@ \divide\dimen@\tw@ \global\dimen3=\dimen@ \dimen@=\Yc \advance\dimen@\B@ \divide\dimen@\tw@ \global\dimen5=\dimen@ \xycontrolpt@{3}% \splinetrace@{3: \the\Xc, \the\Yc}% \expandafter\count@\xycrvcnt@\relax %%% very important to |\relax| \ifnum\count@=3\relax \advance\Xc-\A@ \divide\Xc by4\advance\Xc\dimen3\relax \advance\Yc-\B@ \divide\Yc by4\advance\Yc\dimen5\relax \enter@{\Xp=\the\Xc \Yp=\the\Yc \crv@cnt@=\@ne\relax \noexpand\lastbspline@ }% \else \advance\Xc 7\dimen3\advance\Xc-2\A@ \divide\Xc by6\relax \advance\Yc 7\dimen5\advance\Yc-2\B@ \divide\Yc by6\relax \enter@{\Xp=\the\Xc \Yp=\the\Yc \crv@cnt@=\@ne\relax \noexpand\middlebspline@ }% \fi \adjustmaxmin@ \czeroEdge@ \bsplined@ \splineset@ \leave@ } \xydef@\lastbspline@@{\bgroup \advance\crv@cnt@\@ne \edef\segmentnum@{\the\crv@cnt@}% \splinetrace@{<: \the\Xp, \the\Yp}% \advance\crv@cnt@\@ne \xycontrolpt@{\crv@cnt@}% \global\dimen3=\Xc \global\dimen5=\Yc \splinetrace@{\the\crv@cnt@: \the\Xc, \the\Yc}% \advance\crv@cnt@\m@ne \xycontrolpt@{\crv@cnt@}% \splinetrace@{\the\crv@cnt@: \the\Xc, \the\Yc}% \A@=\Xc \advance\A@\dimen3\divide\A@\tw@ \B@=\Yc \advance\B@\dimen5\divide\B@\tw@ \adjustmaxmin@ \leave@ \splinetrace@{>: \the\Xc, \the\Yc}% \bsplined@ \splineset@ \leave@ }% \xylet@\lastbspline@=\lastbspline@@ \xydef@\middlebspline@@{% \advance\crv@cnt@\@ne \edef\segmentnum@{\the\crv@cnt@}% \splinetrace@{<: \the\Xp, \the\Yp}% \bgroup \xycontrolpt@{\crv@cnt@}\A@=2\Xc \B@=2\Yc \splinetrace@{\the\crv@cnt@: \the\Xc, \the\Yc}% \advance\crv@cnt@\@ne \xycontrolpt@{\crv@cnt@}% \splinetrace@{\the\crv@cnt@: \the\Xc \the\Yc}% \advance\A@\Xc \divide\A@ by3\advance\B@\Yc \divide\B@ by3\relax \advance\Xc\A@ \divide\Xc\tw@ \advance\Yc\B@ \divide\Yc\tw@ \global\dimen3=\Xc \global\dimen5=\Yc \advance\crv@cnt@\@ne \xycontrolpt@{\crv@cnt@}% \splinetrace@{\the\crv@cnt@: \the\Xc \the\Yc}% \expandafter\count@\xycrvcnt@\relax \ifnum\crv@cnt@<\count@\relax \advance\Xc 7\dimen3\advance\Xc-2\A@ \divide\Xc by6\relax \advance\Yc 7\dimen5\advance\Yc-2\B@ \divide\Yc by6\relax \enter@{\Xp=\the\Xc \Yp=\the\Yc \crv@cnt@=\segmentnum@\relax \noexpand\middlebspline@}% \else \advance\Xc-\A@ \divide\Xc by4\advance\Xc \dimen3\relax \advance\Yc-\B@ \divide\Yc by4\advance\Yc \dimen5\relax \enter@{\Xp=\the\Xc \Yp=\the\Yc \noexpand\lastbspline@}% \fi \adjustmaxmin@ \bsplined@ \splineset@ \leave@ } \xylet@\middlebspline@=\middlebspline@@ \DOCMODE) \DOCMODE( \xydef@\scanbspline@{\splined@@@} \DOCMODE) \DOCMODE( \xydef@\bsplined@{\cubiced@ \expandafter\ifx\xysplineedges@\relax\relax\else %\bgroup \readsplineparams@ \count@@=\dimen7\divide\count@@\p@ \advance\dimen7-\count@@\p@ \advance\count@@\@ne \count@=\dimen5\divide\count@\p@ \advance\dimen5-\count@\p@ \advance\count@\@ne \splinetrace@{segment \segmentnum@:\the\count@,\the\count@@:\xysplineedges@}% \splinetrace@{params:\xysplineparams@}% \expandafter\ifnum\segmentnum@=\count@ \else \expandafter\ifnum\segmentnum@<\count@\dimen5=\p@ \else\dimen5=\z@ \fi\fi \expandafter\ifnum\segmentnum@=\count@@ \multiply\dimen7by\m@ne \advance\dimen7by\p@ \else \expandafter\ifnum\segmentnum@>\count@@\dimen5=\p@\dimen7=\p@ \else\dimen7=\z@ \fi\fi \expandafter\edef\xysplineedges@{% \noexpand\z@;\the\dimen5,\noexpand\z@;\the\dimen7,}% \edef\tmp@{%\egroup \noexpand\def\expandafter\noexpand\xysplineedges@{\xysplineedges@}}% \tmp@ \fi }% \DOCMODE) These macros select the correct B\'ezier control points for each segment of the spline. This is needed for finding places on the constructed curves. In general there are 5 types of segment: first, second, middle, penultimate, final. The conversion from B-spline to B\'ezier is slightly different for each type. For middle segments the B\'ezier control points are determined in the following way: \begin{eqnarray*} X_{B_i}^{(1)} &=& {1\over3}\left( 2X^{(i)} + X^{(i+1)}\right)\\ X_{B_i}^{(2)} &=& {1\over3}\left( X^{(i)} + 2X^{(i+1)}\right)\\ X_{B_i}^{(0)} &=& {1\over6}\left( X^{(i-1)} + 4X^{(i)} + X^{(i+1)}\right)\\ &=& {1\over6}\left( X^{(i-1)} + 7X_{B_i}^{(1)} - 2X_{B_i}^{(2)}\right)\\ X_{B_i}^{(3)} &=& {1\over6}\left( X^{(i)} + 4X^{(i+1)} + X^{(i+2)}\right)\\ &=& {1\over6}\left( X^{(i+2)} + 7X_{B_i}^{(2)} - 2X_{B_i}^{(1)}\right)\\ \end{eqnarray*} \DOCMODE( \xydef@\bsegment@@ii@iii{% \xycontrolpt@{\count@@}\global\dimen3=\Xc \global\dimen5=\Yc \splinetrace@{\the\count@@: \the\Xc \the\Yc}% \advance\count@@\@ne \xycontrolpt@{\count@@}\A@=\Xc \B@=\Yc \advance\A@ by2\dimen3\divide\A@ by3\relax \advance\B@ by2\dimen5\divide\B@ by3\relax \dimen@=\dimen3 \advance\dimen@ by2\Xc \divide\dimen@\thr@@ \global\dimen3=\dimen@ \dimen@=\dimen5 \advance\dimen@ by2\Yc \divide\dimen@\thr@@ \global\dimen5=\dimen@ \advance\count@@\m@ne } \xydef@\bsegment@@i{\bgroup \advance\count@@\m@ne \xycontrolpt@{\count@@}% \splinetrace@{\the\count@@: \the\Xc \the\Yc}% \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ \advance\Xp-2\dimen3\advance\Xp by7\A@ \divide\Xp by6\relax \advance\Yp-2\dimen5\advance\Yp by7\B@ \divide\Yp by6\relax \xydef@\bsegment@@iv{% \advance\count@@\tw@ \xycontrolpt@{\count@@}% \splinetrace@{\the\count@@: \the\Xc \the\Yc}% \advance\Xc by7\dimen3\advance\Xc-2\A@ \divide\Xc by6\relax \advance\Yc by7\dimen5\advance\Yc-2\B@ \divide\Yc by6\relax \DOCMODE) For the second segment the expression for $X_{B_2}^{(0)}$ is different. Similarly the expression for $X_{B_{n-1}}^{(3)}$ is altered for the penultimate segment. \begin{eqnarray*} X_{B_2}^{(0)} &=& {1\over4}\left( X^{(1)} + 4X_{B_2}^{(1)} - X_{B_2}^{(2)}\right)\\ X_{B_{n-1}}^{(3)} &=& {1\over4}\left( X^{(3)} + 4X_{B_{n-1}}^{(2)} - X_{B_{n-1}}^{(1)}\right)\\ \end{eqnarray*} \DOCMODE( \xydef@\bsegment@@ii@i{\bgroup \advance\count@@\m@ne \xycontrolpt@{\count@@}% \splinetrace@{\the\count@@: \the\Xc \the\Yc}% \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ \advance\Xp-\dimen3\advance\Xp by4\A@ \divide\Xp by4\relax \advance\Yp-\dimen5\advance\Yp by4\B@ \divide\Yp by4\relax \xydef@\bsegment@@y@iv{% \advance\count@@\tw@ \xycontrolpt@{\count@@}% \splinetrace@{\the\count@@: \the\Xc \the\Yc}% \advance\Xc by4\dimen3\advance\Xc-\A@ \divide\Xc by4\relax \advance\Yc by4\dimen5\advance\Yc-\B@ \divide\Yc by4\relax \DOCMODE) For the first segment the first three B\'ezier control points are always determined in the same way: \begin{eqnarray*} X_{B_1}^{(0)} = X^{(0)} & X_{B_1}^{(1)} = X^{(1)} & X_{B_1}^{(2)} = {1\over2}(X_{B_1}^{(1)}+X^{(2)})\\ \hbox{3+~segments}& & X_{B_1}^{(3)} = {1\over6}(7X_{B_1}^{(2)} - 2X_{B_1}^{(1)} + X^{(3)})\\ \hbox{~2~segments}& & X_{B_1}^{(3)} = {1\over4}(4X_{B_1}^{(2)} - X_{B_1}^{(1)} + X^{(3)})\\ \end{eqnarray*} \DOCMODE( \xydef@\bsegment@i{% \xycontrolpt@\z@ \Xp=\the\Xc \Yp=\the\Yc % \ifx\cv@start\relax % \bgroup\csname cv@0\endcsname % \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ % \else\xycontrolpt@\z@ \Xp=\the\Xc \Yp=\the\Yc\fi \splinetrace@{0: \the\Xp \the\Yp}% \xycontrolpt@\@ne \splinetrace@{1: \the\Xc \the\Yc}\A@=\Xc \B@=\Yc \xycontrolpt@\tw@ \dimen@=\Xc \advance\dimen@\A@ \divide\dimen@\tw@ \global\dimen3=\dimen@ \splinetrace@{2: \the\Xc \the\Yc}% \dimen@=\Yc \advance\dimen@\B@ \divide\dimen@\tw@ \global\dimen5=\dimen@ } \xydef@\bsegment@i@iv{% \xycontrolpt@{3}% \splinetrace@{3: \the\Xc \the\Yc}% \advance\Xc by7\dimen3\advance\Xc-2\A@ \divide\Xc by6\relax \advance\Yc by7\dimen5\advance\Yc-2\B@ \divide\Yc by6\relax \DOCMODE) The last segment is determined symmetrically from the final four control points: \begin{eqnarray*} X_{B_n}^{(3)} = X^{(n+2)} & X_{B_n}^{(2)} = X^{(n+1)} & X_{B_n}^{(1)} = {1\over2}(X_{B_n}^{(2)}+X^{(n)})\\ \hbox{3+~segments}& & X_{B_n}^{(0)} = {1\over6}(X^{(n-1)}-2X_{B_n}^{(2)}+7X_{B_n}^{(1)})\\ \hbox{~2~segments}& & X_{B_n}^{(0)} = {1\over4}(X^{(1)}-X_{B_n}^{(2)}+4X_{B_n}^{(1)})\\ \end{eqnarray*} \DOCMODE( \xydef@\bsegment@z{% \expandafter\count@@\xycrvptsnum@ \advance\count@@\@ne \xycontrolpt@{\the\count@@}\relax\edef\tmp@{\Xc=\the\Xc \Yc=\the\Yc}% \splinetrace@{\the\count@@: \the\Xc, \the\Yc}% \advance\count@@\m@ne \xycontrolpt@{\the\count@@}% \splinetrace@{\the\count@@: \the\Xc, \the\Yc}% \global\dimen3=\Xc \global\dimen5=\Yc \advance\count@@\m@ne \xycontrolpt@{\the\count@@}% \splinetrace@{\the\count@@: \the\Xc, \the\Yc}% \A@=\Xc \advance\A@\dimen3\divide\A@\tw@ \B@=\Yc \advance\B@\dimen5\divide\B@\tw@ \tmp@ \xydef@\bsegment@z@i{% \expandafter\count@@\xycrvptsnum@\relax\advance\count@@-\tw@ \edef\tmp@{\Xc=\the\Xc \Yc=\the\Yc}% \xycontrolpt@{\count@@}\Xp=\the\Xc \Yp=\the\Yc \splinetrace@{\the\count@@: \the\Xc, \the\Yc}% \advance\Xp by7\A@ \advance\Xp-2\dimen3\divide\Xp by6\relax \advance\Yp by7\B@ \advance\Yp-2\dimen5\divide\Yp by6\relax \tmp@ } \DOCMODE) This is the switching-yard. \DOCMODE( \xydef@\bsplinesegment@#1{\xybsplinemethods@ \count@@=#1\relax \ifnum\count@@=\@ne % first segment \bsegment@i \bsegment@i@iv \else\ifnum\count@@=\tw@ % second segment \bsegment@@ii@iii \bsegment@@ii@i \bsegment@@iv % \else\expandafter\count@\xycrvcnt@\advance\count@\m@ne \else\expandafter\count@\xycrvptsnum@\advance\count@\m@ne \ifnum\count@@=\count@ % last segment \bsegment@z \bsegment@z@i \else\advance\count@\m@ne \ifnum\count@@=\count@ % penult. segment \bsegment@@ii@iii \bsegment@@i \bsegment@@y@iv \else % middle segments \bsegment@@ii@iii \bsegment@@i \bsegment@@iv \fi\fi\fi\fi \def\segmentnum@{\number#1}\bsplined@ } \DOCMODE) Here are the differences for 3 control points. \DOCMODE( \xydef@\bsplinesegment@iii#1{\xybsplinemethods@ \splinetrace@{2 segments; \number#1}% \count@@=#1\relax \ifnum\count@@=\@ne \bsegment@i \xycontrolpt@{3}% \splinetrace@{3: \the\Xc, \the\Yc}% \advance\Xc 4\dimen3\advance\Xc-\A@ \divide\Xc by4\relax \advance\Yc 4\dimen5\advance\Yc-\B@ \divide\Yc by4\relax \else\ifnum\count@@=\tw@ \bsegment@z \bgroup\xycontrolpt@{\@ne}% \splinetrace@{1: \the\Xc, \the\Yc}% \edef\tmp@{\egroup \Xp=\the\Xc \Yp=\the\Yc}\tmp@ \advance\Xp 4\A@ \advance\Xp-\dimen3\divide\Xp by4\relax \advance\Yp 4\B@ \advance\Yp-\dimen5\divide\Yp by4\relax \fi\fi \def\segmentnum@{\number#1}\bsplined@ \DOCMODE) Here are the differences for 4 control points. \DOCMODE( \xydef@\bsplinesegment@iv#1{\xybsplinemethods@ \splinetrace@{3 segments; \number#1}% \count@@=#1\relax \ifnum\count@@=\@ne \bsegment@i \bsegment@i@iv \else\ifnum\count@@=\tw@ \bsegment@@ii@iii \bsegment@@ii@i \bsegment@@y@iv \else\ifnum\count@@=3\relax \bsegment@z \bsegment@z@i \fi\fi\fi \def\segmentnum@{\number#1}\bsplined@ \DOCMODE) \DOCMODE2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \paragraph*{The end \& Log}\leavevmode \DOCMODE( \xyendinput % $Log: xycurve.doc,v $ % Revision 2.12 1994/10/25 03:01:14 ross % Final 3beta release [bug fixes & AMS-LaTeX fitting]. % Revision 2.12 1994/09/05 08:22:11 ross % incorporates some speed-ups, extra documentation % Revision 2.11 1994/07/05 09:27:52 ross % Minor fixes; use 2.11 kernel stack code. % Revision 2.9 1994/06/09 14:50:54 ross % Release 3beta. % Revision 2.8 1994/04/08 10:36:40 ross % Second 3alpha release. % Revision 2.7 1994/03/08 02:06:01 kris % Release 3alpha. % Revision 2.6.9.1 1994/03/07 04:22:46 ross % Last internal 3alpha and pre-2.7 release. % NEW for version 2.7 inspired by Knuth's picmac.tex. \DOCMODE) \DOCMODE3%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%